import asyncio
import base64
import itertools
import json
import os
import random
import shutil
import sys
import time
from decimal import Decimal
from pathlib import Path

import pytest
import safe_radix32
from clarabot_crypto.group_config.block import GroupConfigBlockFlags, GroupConfigBlockMemberFlags
from clarabot_nano_common.common.crypto.keyring import NanoSelfAccountKeyring
from clarabot_nano_common.common.protocol.errors import (
    NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST,
    NANO_REQUEST_ERROR_HANDLER_RESET_REQUEST,
    NANO_REQUEST_ERROR_HANDLER_TARGET_EXISTS,
    NANO_REQUEST_ERROR_HANDLER_DENIED,
    NANO_REQUEST_ERROR_HANDLER_TARGET_NOT_FOUND,
    NANO_REQUEST_ERROR_INVALID_NEW_NAME,
    NANO_REQUEST_ERROR_LOCKED_PATH,
    NANO_REQUEST_ERROR_ADHOC_INVALID,
)
from clarabot_nano_common.common.solr.rest import create_nano_update_url, create_solr_auth_header
from clarabot_nano_common.utility.serialize import msgpack_dumps

from clarabot_nano.indexer.extractor.picoture import PicotureContentExtractor, PicotureExtraction
from clarabot_nano.indexer.sync_data import ImageBucketSelector
from clarabot_nano.utility import parse_environment_flag
from clarabot_nano.utility.algorithm import encode_interleaved_int
from clarabot_nano_tests.picoture.resources import (
    INPUT_ROOT as PICOTURE_INPUT_ROOT,
    OUTPUT_ROOT as PICOTURE_OUTPUT_ROOT,
)
from clarabot_nano_tests.testing.client.manager import GraphQLClientManager
from clarabot_nano_tests.testing.config import room_blocks_from_room_data
from clarabot_nano_tests.testing.core.instance import RequestTestingNanoInstanceHandler
from clarabot_nano_tests.testing.file import change_yaml
from clarabot_nano_tests.testing.gateway import start_nano_wait_ready
from clarabot_nano_tests.testing.server.manager import DevServerManager
from clarabot_nano_tests.testing.solr import run_check_update
from clarabot_nano_tests.utility.path.utility import (
    ComplexStruct,
    is_remote_security_set,
    get_platform_alternative_acute_e,
)


@pytest.mark.skipif(parse_environment_flag("CBNANO_TEST_WITHOUT_SERVER"), reason="server-dependant tests disabled")
@pytest.mark.asyncio
async def test_server_request_process_fs(
    tmpdir,
    reset_user_app_dirs,
    dev_nano_root_dir,
    testing_server_manager: DevServerManager,
    testing_server_user0,
    testing_client_manager: GraphQLClientManager,
    testing_server_reset_data,
):
    flame_jpg = (PICOTURE_INPUT_ROOT / "jpg" / "flame0.jpg").read_bytes()
    sky_png = (PICOTURE_INPUT_ROOT / "png" / "sky.png").read_bytes()
    sky_jpg = (PICOTURE_OUTPUT_ROOT / "png" / "sky.png_t.jpg").read_bytes()

    cs = ComplexStruct.new(tmpdir)
    valid_roots, invalid_roots = cs.split_roots_by_validity()
    r0: Path = valid_roots[0]
    r1: Path = valid_roots[1]

    u1_data = testing_server_reset_data["user_accounts"][1]
    u2_data = testing_server_reset_data["user_accounts"][2]
    u3_data = testing_server_reset_data["user_accounts"][3]
    d0_data = testing_server_reset_data["dist_accounts"][0]

    rooms = testing_server_user0["rooms"][:4]
    drives = [
        {
            "path": str(r0),
            "rooms": [safe_radix32.encode_fw(rooms[0]["id"]), safe_radix32.encode_fw(rooms[2]["id"])],
        },
        {
            "path": str(r1),
            "rooms": [safe_radix32.encode_fw(rooms[1]["id"])],
        },
    ]
    room_blocks = {
        safe_radix32.encode_fw(rooms[0]["id"]): room_blocks_from_room_data(rooms[0]),
        safe_radix32.encode_fw(rooms[1]["id"]): room_blocks_from_room_data(rooms[1]),
        safe_radix32.encode_fw(rooms[2]["id"]): room_blocks_from_room_data(rooms[2]),
    }

    async with start_nano_wait_ready(testing_server_manager, reset_user_app_dirs, dev_nano_root_dir):
        pass  # create new instance config

    drive_hook_info = {"/drive/test": {"errors": [], "mode": ["test"]}}
    with change_yaml(Path(reset_user_app_dirs[0]) / "local.yml") as config:
        config["hook_drive_info"] = [sys.executable, "-c", f"print({json.dumps(drive_hook_info)!r})"]

    async with RequestTestingNanoInstanceHandler(
        reset_user_app_dirs,
        dev_nano_root_dir,
        testing_server_manager,
        testing_server_user0,
        testing_client_manager,
        drives,
        room_blocks,
    ).run() as (pm, debug_info, gateway_port, gateway_key, session_id, remote_yaml, nano_id, u0wsc):
        await testing_server_manager.call_throttling_reset(u0wsc)
        session = await testing_server_manager.session()
        dbg_config = await pm.get_children_config(session, debug_info["port"])
        u_url = create_nano_update_url(dbg_config["state"]["solr_port"], dbg_config["state"]["solr_core"])
        auth_h = create_solr_auth_header(dbg_config["state"]["solr_key"])

        # u0wsc = await testing_client_manager.create_logged_in_wsc(testing_server_user0)
        u1wsc = await testing_client_manager.create_logged_in_wsc(u1_data)
        u2wsc = await testing_client_manager.create_logged_in_wsc(u2_data)
        u3wsc = await testing_client_manager.create_logged_in_wsc(u3_data)
        d0wsc = await testing_client_manager.create_logged_in_wsc(d0_data)
        anon_s = await testing_client_manager.session("anon")
        anon_wsc = await testing_client_manager.wsc(anon_s)
        anon_kr = NanoSelfAccountKeyring.generate_anonymous()
        u0_kr = testing_server_user0["_acc_kr"]
        r0_id = rooms[0]["id"]

        # setup access control for testing
        await testing_client_manager.nano_request_response(
            msgpack_dumps(
                await testing_client_manager.prepare_request_with_mutation_id(
                    {
                        "type": "ROOM-CFG",
                        "room_id": safe_radix32.encode_fw(rooms[0]["id"]),
                        "member_flags": {
                            safe_radix32.encode_fw(u1_data["account_id"]): GroupConfigBlockMemberFlags.ACTIVE,
                            safe_radix32.encode_fw(u2_data["account_id"]): GroupConfigBlockMemberFlags.ACTIVE_NEGATE,
                            safe_radix32.encode_fw(u3_data["account_id"]): GroupConfigBlockMemberFlags.ACTIVE
                            | GroupConfigBlockMemberFlags.DRIVE_WRITE,
                            safe_radix32.encode_fw(d0_data["account_id"]): GroupConfigBlockMemberFlags.ACTIVE
                            | GroupConfigBlockMemberFlags.DRIVE_UPLOAD,
                        },
                        "group_flags": GroupConfigBlockFlags.ALLOW_ANONYMOUS,
                    },
                    "dbg_fs0",
                    rooms[0]["id"],
                    None,
                    u0wsc,
                    u0_kr,
                    u0_kr,
                )
            ),
            rooms[0]["id"],
            None,
            u0wsc,
            u0_kr,
            u0_kr,
        )
        await testing_client_manager.nano_request_response(
            msgpack_dumps(
                await testing_client_manager.prepare_request_with_mutation_id(
                    {
                        "type": "ROOM-CFG",
                        "room_id": safe_radix32.encode_fw(rooms[1]["id"]),
                        "member_flags": {
                            safe_radix32.encode_fw(u1_data["account_id"]): GroupConfigBlockMemberFlags.ACTIVE,
                            safe_radix32.encode_fw(u2_data["account_id"]): GroupConfigBlockMemberFlags.ACTIVE_NEGATE,
                            safe_radix32.encode_fw(u3_data["account_id"]): GroupConfigBlockMemberFlags.ACTIVE
                            | GroupConfigBlockMemberFlags.DRIVE_WRITE,
                            safe_radix32.encode_fw(d0_data["account_id"]): GroupConfigBlockMemberFlags.ACTIVE
                            | GroupConfigBlockMemberFlags.DRIVE_UPLOAD,
                        },
                        "group_flags": GroupConfigBlockFlags.ALLOW_ANONYMOUS_NEGATE,
                    },
                    "dbg_fs0",
                    rooms[0]["id"],
                    None,
                    u0wsc,
                    u0_kr,
                    u0_kr,
                )
            ),
            rooms[1]["id"],
            None,
            u0wsc,
            u0_kr,
            u0_kr,
        )

        # fs-get and fs-ls migration helpers
        new_listing = True

        # create some broken/invalid symlinks to test new_path() behaviour against.
        sym_contained_target = "sym_trg"
        (r0 / sym_contained_target).write_bytes(b"syminyms")
        sym_broken_contained = []
        sym_broken_leaves = []
        sym_contained = []
        sym_leaves = []
        sym_cycle = []
        for i in range(3):
            sym_broken_contained.append(tmp := (r0 / f"sym_bc_{i}"))
            tmp.symlink_to("_ neve_ exists_here_no")
            sym_broken_leaves.append(tmp := (r0 / f"sym_bl_{i}"))
            tmp.symlink_to(
                "C:\\tmp\\_ neve_ exists_here_no" if sys.platform.startswith("win") else "/tmp/_ neve_ exists_here_no"
            )
            sym_contained.append(tmp := (r0 / f"sym_c_{i}"))
            tmp.symlink_to(sym_contained_target)
            sym_leaves.append(tmp := (r0 / f"sym_l_{i}"))
            tmp.symlink_to("..")
            sym_cycle.append(tmp := (r0 / f"sym_cyc_{i}"))
            tmp.symlink_to(tmp)

        # FS-GET
        # check structural consistency
        async def _fetch(
            ls_fn_, room_, root_path_, query_path_, allowed_, unresolved_, query_wsc_, query_kr_, depth=0, **req_kwargs
        ):
            if depth > 2:
                return  # stop cyclic symlinks
            elif depth == 2 and random.random() > 0.1:  # allow some of this depth
                return
            await testing_server_manager.call_throttling_reset(query_wsc_)
            if query_kr_.id == u0_kr.id:
                query_room_kr = None
            else:
                query_room_kr = room_["_room_kr"]
            res_ = await ls_fn_(
                query_path_,
                room_["id"],
                query_wsc_,
                query_kr_,
                u0_kr,
                room_kr=query_room_kr,
                ls_path=root_path_,
                ls_allowed=allowed_,
                ls_unresolved=unresolved_,
                **req_kwargs,
            )
            dirs_, files_, unres_ = res_ if res_ else ([], [], [])
            for sd_ in dirs_:
                await _fetch(
                    ls_fn_,
                    room_,
                    root_path_ / sd_,
                    f"{query_path_}/{sd_}" if query_path_ else sd_,
                    allowed_,
                    unresolved_,
                    query_wsc_,
                    query_kr_,
                    depth + 1,
                    **req_kwargs,
                )
            for f_ in files_:
                await testing_client_manager.download_room(
                    f"{query_path_}/{f_}" if query_path_ else f_,
                    room_["id"],
                    query_wsc_,
                    query_kr_,
                    u0_kr,
                    room_kr=query_room_kr,
                    file_path=root_path_ / f_,
                    **req_kwargs,
                )
            for u_ in unres_:
                await testing_client_manager.download_room(
                    f"{query_path_}/{u_}" if query_path_ else u_,
                    room_["id"],
                    query_wsc_,
                    query_kr_,
                    u0_kr,
                    room_kr=query_room_kr,
                    error=NANO_REQUEST_ERROR_HANDLER_TARGET_NOT_FOUND,
                    **req_kwargs,
                )
                await ls_fn_(
                    f"{query_path_}/{u_}" if query_path_ else u_,
                    room_["id"],
                    query_wsc_,
                    query_kr_,
                    u0_kr,
                    room_kr=query_room_kr,
                    error=NANO_REQUEST_ERROR_HANDLER_TARGET_NOT_FOUND,
                    **req_kwargs,
                )

        for room, root_path, rnx, server_error in [(rooms[0], r0, "", None), (rooms[1], r1, ".", "Invalid argument!")]:
            allowed, unresolved = cs.get_allowed_items(root_path, True, True)
            if root_path == r0:
                allowed.add(sym_contained_target)
                for tmp_ in [sym_broken_contained, sym_broken_leaves, sym_contained, sym_leaves, sym_cycle]:
                    allowed.update(x_.name for x_ in tmp_)
                for tmp_ in [sym_broken_leaves, sym_leaves]:  # it's enough to add the issues from containment policy
                    unresolved.update(x_.name for x_ in tmp_)
            for listing_fn in [testing_client_manager.old_listing_room] + (
                [testing_client_manager.listing_room] if new_listing else []
            ):
                await _fetch(listing_fn, room, root_path, rnx, allowed, unresolved, u0wsc, u0_kr)
                await _fetch(listing_fn, room, root_path, rnx, allowed, unresolved, u1wsc, u1_data["_acc_kr"])
                await _fetch(
                    listing_fn,
                    room,
                    root_path,
                    rnx,
                    allowed,
                    unresolved,
                    u2wsc,
                    u2_data["_acc_kr"],
                    server_error=server_error,
                )
                await _fetch(
                    listing_fn,
                    room,
                    root_path,
                    rnx,
                    allowed,
                    unresolved,
                    anon_wsc,
                    anon_kr,
                    ephemeral_pk=anon_kr.get_public_key(anon_kr.CURVE25519),
                    server_error=server_error,
                )

        # special file sizes (in terms of chunking)
        fp = r0 / "file_sizes_xy"
        for s, pc in [(0, 0), (1, 1), (500_000, 1), (500_001, 2), (1_000_000, 2), (1_000_001, 3)]:
            fp.write_bytes(os.urandom(s))
            await testing_client_manager.download_room(
                fp.name, rooms[0]["id"], u0wsc, u0_kr, u0_kr, file_path=fp, part_count=pc
            )

        large_dir = r0 / "large"
        large_dir.mkdir()
        large_s_dir = large_dir / "s"
        large_s_dir.mkdir()

        # img file for peek test
        img_file = large_dir / "im_an_img.png"
        img_file.write_bytes(PicotureContentExtractor.CHECK_PNG)
        img_peek = {
            "name": img_file.name,
            "mtime": img_file.stat().st_mtime,
            "size": img_file.stat().st_size,
        }
        ip = "large/im_an_img.png"
        s_file = large_s_dir / "some.txt"
        s_file.write_bytes(b"asd")

        # img files for multipart thumbnails test
        thumbs_dir = r0 / "thumbs"
        thumbs_dir.mkdir()
        thumbs_n = 8_000_000 // len(sky_jpg)
        assert thumbs_n > ImageBucketSelector.BUCKET_MAX_SIZE
        for i in range(thumbs_n):
            (thumbs_dir / f"{i:04d}.png").write_bytes(sky_png)

        # large file
        r0_kr = rooms[0]["_room_kr"]
        big_file = large_dir / "im_so_big.bin"

        def _write_big_file():
            with open(big_file, "wb") as f:
                for _ in range(151):
                    f.write(os.urandom(1420) * 751)

        await asyncio.get_running_loop().run_in_executor(None, _write_big_file)
        fp = "large/im_so_big.bin"
        await testing_client_manager.download_room(fp, rooms[0]["id"], u0wsc, u0_kr, u0_kr, file_path=big_file)
        await testing_client_manager.download_room(
            fp, rooms[0]["id"], u1wsc, u1_data["_acc_kr"], u0_kr, r0_kr, file_path=big_file
        )
        await testing_client_manager.download_room(
            fp, rooms[0]["id"], u2wsc, u2_data["_acc_kr"], u0_kr, r0_kr, file_path=big_file
        )
        await testing_client_manager.download_room(
            fp,
            rooms[0]["id"],
            anon_wsc,
            anon_kr,
            u0_kr,
            r0_kr,
            file_path=big_file,
            ephemeral_pk=anon_kr.get_public_key(anon_kr.CURVE25519),
        )

        if new_listing:
            # consistency-reset tests for fs-ls
            cr_d0 = r0 / "_cr_d0"
            cr_d1 = r0 / "_cr_d1"
            cr_d0.mkdir()
            cr_d1.mkdir()
            cr_ck0, cr_c0, listed = await testing_client_manager.listing_room_raw(
                cr_d0.name, rooms[0]["id"], u0wsc, u0_kr, u0_kr
            )
            assert not listed
            # test successful query of stale state by explicit "continue" request (server cached parts and 0 from nano)
            cr_d0_f0 = cr_d0 / "cr_f0"
            cr_d0_f0.write_bytes(b"cr0")
            cr_d1_f1 = cr_d1 / "cr_f1"
            cr_d1_f1.write_bytes(b"cr1")
            cr_ck1, cr_c1, listed = await testing_client_manager.listing_room_raw(
                cr_d0.name, rooms[0]["id"], u0wsc, u0_kr, u0_kr, cache_key=cr_ck0, part=1
            )
            assert cr_ck1 == cr_ck0
            assert not listed
            # test reset by query cache-key for different path
            cr_ck2, cr_c2, listed = await testing_client_manager.listing_room_raw(
                cr_d1.name, rooms[0]["id"], u0wsc, u0_kr, u0_kr, cache_key=cr_ck0, part=1
            )
            assert cr_ck2 != cr_ck0
            assert sorted(l_["name"] for l_ in listed) == [cr_d1_f1.name]
            # test reset by query cache-key for different ino behind same path
            shutil.rmtree(cr_d1)
            shutil.move(cr_d0, cr_d1)
            cr_ck3, cr_c3, listed = await testing_client_manager.listing_room_raw(
                cr_d1.name, rooms[0]["id"], u0wsc, u0_kr, u0_kr, cache_key=cr_ck2, part=1
            )
            assert cr_ck3 != cr_ck2
            assert sorted(l_["name"] for l_ in listed) == [cr_d0_f0.name]
            # test reset by missing cached part
            await testing_server_manager.call_nano_clear_cache()
            cr_ck4, cr_c4, listed = await testing_client_manager.listing_room_raw(
                cr_d1.name, rooms[0]["id"], u0wsc, u0_kr, u0_kr, cache_key=cr_ck3, part=1
            )
            assert cr_ck4 == cr_ck3
            assert sorted(l_["name"] for l_ in listed) == [cr_d0_f0.name]
            # test query to deleted entity will error for resolution
            shutil.rmtree(cr_d1)
            await testing_client_manager.listing_room_raw(
                cr_d1.name,
                rooms[0]["id"],
                u0wsc,
                u0_kr,
                u0_kr,
                cache_key=cr_ck4,
                part=1,
                error=NANO_REQUEST_ERROR_HANDLER_TARGET_NOT_FOUND,
            )

        bad_req_ls_paths = [None, [""]]
        if new_listing:
            bad_req_ls_paths.append("large/im_an_img.png")
        for p in bad_req_ls_paths:
            await testing_client_manager.listing_room(
                p, rooms[0]["id"], u0wsc, u0_kr, u0_kr, error=NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST
            )
        for p in ["..", "/tmp", "_neve. exist where"]:
            await testing_client_manager.listing_room(
                p, rooms[0]["id"], u0wsc, u0_kr, u0_kr, error=NANO_REQUEST_ERROR_HANDLER_TARGET_NOT_FOUND
            )
        # TODO: create coverage for fs-get error branches that are hard to simulate
        # TODO: create coverage for fs-ls error branches that are hard to simulate

        # wait indexer to finish
        assert await pm.assert_core_wait_idle_indexer(session, debug_info["port"], idle_after=5.0, timeout=600.0)

        # adhoc token testing for FS
        for p in [55, b"xy"]:
            await testing_client_manager.create_adhoc_fs(
                p, r0_id, "adhoc-u0", u0wsc, u0_kr, u0_kr, error=NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST
            )
        for ttl in [55, "55"]:
            await testing_client_manager.create_adhoc_fs(
                ip, r0_id, "adhoc-u0", u0wsc, u0_kr, u0_kr, ttl=ttl, error=NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST
            )
        for p in ["..", "/tmp", "_neve. exist where"]:
            await testing_client_manager.create_adhoc_fs(
                p, r0_id, "adhoc-u0", u0wsc, u0_kr, u0_kr, error=NANO_REQUEST_ERROR_HANDLER_TARGET_NOT_FOUND
            )

        image_aht, image_aht_kr = await testing_client_manager.create_adhoc_fs(
            ip, r0_id, "adhoc-u1", u1wsc, u1_data["_acc_kr"], u0_kr, r0_kr
        )
        large_aht, large_aht_kr = await testing_client_manager.create_adhoc_fs(
            "large", r0_id, "adhoc-u0", u0wsc, u0_kr, u0_kr, ttl=600
        )

        # test room-reset
        for t in [image_aht, large_aht]:
            await testing_client_manager.listing_room(
                "_noExist", r0_id, u0wsc, u0_kr, u0_kr, error=NANO_REQUEST_ERROR_HANDLER_TARGET_NOT_FOUND, adhoc_token=t
            )
        assert (await testing_client_manager.count_adhoc_tokens(r0_id, u0wsc, u0_kr, u0_kr)) > 0
        await testing_client_manager.reset_adhoc_tokens(r0_id, "adhoc-u0", u0wsc, u0_kr, u0_kr)
        assert (await testing_client_manager.count_adhoc_tokens(r0_id, u0wsc, u0_kr, u0_kr)) == 0
        for t in [image_aht, large_aht]:
            await testing_client_manager.listing_room(
                "_noExist", r0_id, u0wsc, u0_kr, u0_kr, error=NANO_REQUEST_ERROR_ADHOC_INVALID, adhoc_token=t
            )

        root_aht, root_aht_kr = await testing_client_manager.create_adhoc_fs(
            "", r0_id, "adhoc-u1", u1wsc, u1_data["_acc_kr"], u0_kr, r0_kr
        )
        image_aht, image_aht_kr = await testing_client_manager.create_adhoc_fs(
            ip, r0_id, "adhoc-u1", u1wsc, u1_data["_acc_kr"], u0_kr, r0_kr
        )
        large_aht, large_aht_kr = await testing_client_manager.create_adhoc_fs(
            "large", r0_id, "adhoc-u0", u0wsc, u0_kr, u0_kr, ttl=6000
        )

        # adhoc GET and LS
        for p in ["..", "/tmp", "_neve. exist where"]:
            await testing_client_manager.listing_room(
                p, r0_id, u0wsc, u0_kr, u0_kr, error=NANO_REQUEST_ERROR_HANDLER_TARGET_NOT_FOUND, adhoc_token=large_aht
            )
            await testing_client_manager.old_listing_room(
                p, r0_id, u0wsc, u0_kr, u0_kr, error=NANO_REQUEST_ERROR_HANDLER_TARGET_NOT_FOUND, adhoc_token=large_aht
            )
            await testing_client_manager.download_room(
                p, r0_id, u0wsc, u0_kr, u0_kr, error=NANO_REQUEST_ERROR_HANDLER_TARGET_NOT_FOUND, adhoc_token=large_aht
            )
        for listing_fn in [testing_client_manager.old_listing_room] + (
            [testing_client_manager.listing_room] if new_listing else []
        ):
            await _fetch(
                listing_fn,
                rooms[0],
                large_dir,
                "",
                None,
                None,
                u0wsc,
                u0_kr,
                adhoc_kr=large_aht_kr,
                adhoc_token=large_aht,
            )
            await _fetch(
                listing_fn,
                rooms[0],
                large_dir,
                "",
                None,
                None,
                anon_wsc,
                anon_kr,
                adhoc_kr=large_aht_kr,
                adhoc_token=large_aht,
                ephemeral_pk=anon_kr.get_public_key(anon_kr.CURVE25519),
            )

        # adhoc PEEK
        for p in ["..", "/tmp", "_neve. exist where", "a/a"]:
            await testing_client_manager.peek_room(
                p,
                rooms[0]["id"],
                anon_wsc,
                anon_kr,
                u0_kr,
                # NOTE: no room keyring r0_kr
                adhoc_kr=image_aht_kr,
                adhoc_token=image_aht,
                ephemeral_pk=anon_kr.get_public_key(anon_kr.CURVE25519),
                part_count=0,
                error=NANO_REQUEST_ERROR_HANDLER_TARGET_NOT_FOUND,
            )
        root_res = await testing_client_manager.peek_room(
            ip,
            rooms[0]["id"],
            anon_wsc,
            anon_kr,
            u0_kr,
            # NOTE: no room keyring r0_kr
            adhoc_kr=root_aht_kr,
            adhoc_token=root_aht,
            ephemeral_pk=anon_kr.get_public_key(anon_kr.CURVE25519),
            part_count=0,
        )
        res = await testing_client_manager.peek_room(
            "",
            rooms[0]["id"],
            anon_wsc,
            anon_kr,
            u0_kr,
            r0_kr,  # NOTE: room kr as well
            adhoc_kr=image_aht_kr,
            adhoc_token=image_aht,
            ephemeral_pk=anon_kr.get_public_key(anon_kr.CURVE25519),
            part_count=0,
        )
        assert root_res == res
        thumbnail = res.pop("img0", None)
        assert res == img_peek
        assert PicotureExtraction.is_image_valid(thumbnail) and len(thumbnail) < 1000, str(thumbnail)
        for p in ["..", "/tmp", "_neve. exist where", "a/a"]:
            await testing_client_manager.peek_room(
                p,
                rooms[0]["id"],
                anon_wsc,
                anon_kr,
                u0_kr,
                adhoc_kr=large_aht_kr,
                adhoc_token=large_aht,
                ephemeral_pk=anon_kr.get_public_key(anon_kr.CURVE25519),
                part_count=0,
                error=NANO_REQUEST_ERROR_HANDLER_TARGET_NOT_FOUND,
            )
        assert await testing_client_manager.peek_room(
            "",
            rooms[0]["id"],
            anon_wsc,
            anon_kr,
            u0_kr,
            r0_kr,
            adhoc_kr=large_aht_kr,
            adhoc_token=large_aht,
            ephemeral_pk=anon_kr.get_public_key(anon_kr.CURVE25519),
            part_count=0,
        ) == {
            "name": "large",
            "mtime": large_dir.stat().st_mtime,
        }
        res = await testing_client_manager.peek_room(
            img_file.name,
            rooms[0]["id"],
            anon_wsc,
            anon_kr,
            u0_kr,
            adhoc_kr=large_aht_kr,
            adhoc_token=large_aht,
            ephemeral_pk=anon_kr.get_public_key(anon_kr.CURVE25519),
            part_count=0,
        )
        thumbnail = res.pop("img0", None)
        assert res == img_peek
        assert PicotureExtraction.is_image_valid(thumbnail) and len(thumbnail) < 1000, str(thumbnail)

        # FS-PEEK (+preview)
        assert await testing_client_manager.peek_room("large", rooms[0]["id"], u0wsc, u0_kr, u0_kr, part_count=0) == {
            "name": "large",
            "mtime": large_dir.stat().st_mtime,
        }
        assert await testing_client_manager.peek_room(fp, rooms[0]["id"], u0wsc, u0_kr, u0_kr, part_count=0) == {
            "name": big_file.name,
            "mtime": big_file.stat().st_mtime,
            "size": big_file.stat().st_size,
        }
        res = await testing_client_manager.peek_room(ip, rooms[0]["id"], u0wsc, u0_kr, u0_kr, part_count=0)
        thumbnail = res.pop("img0", None)
        assert res == img_peek
        assert PicotureExtraction.is_image_valid(thumbnail) and len(thumbnail) < 1000, str(thumbnail)
        # test no image data at all
        res = await testing_client_manager.peek_room(ip, rooms[0]["id"], u0wsc, u0_kr, u0_kr, options=0, part_count=0)
        assert res == img_peek

        # test preview-only
        res, preview = await testing_client_manager.peek_room(
            ip, rooms[0]["id"], u0wsc, u0_kr, u0_kr, options=2, part_count=1
        )
        assert res == img_peek
        assert PicotureExtraction.is_image_valid(preview) and len(preview) < 1000, str(preview[:1000])

        # replace the indexed image data to check chunked transfer (hacky, we replace the mtime as well)
        flame_jpg_size = encode_interleaved_int(2480, 3508)
        ip_upd = {
            "path": f"{r0.as_posix()}/{ip}",
            "file": True,
            "accessors": {"add-distinct": f"{r0.as_posix()}//{ip}"},
            "size": img_file.stat().st_size,
            "mtime": int(Decimal(img_peek["mtime"] - 13) * 1000000000),
            "img_data": base64.b64encode(sky_jpg).decode("ascii"),
            # NOTE: For testing the search results, this is correct. However, later (if actual bucketing
            # is implemented) this will only be a gross simplification.
            "img_bucket": 1,
            "preview_data": base64.b64encode(flame_jpg).decode("ascii"),
            "preview_size": flame_jpg_size,
        }
        await run_check_update(session, u_url, auth_h, [ip_upd])
        # test all options combinations
        res = await testing_client_manager.peek_room(ip, rooms[0]["id"], u0wsc, u0_kr, u0_kr, part_count=0)
        assert res == {"img0": sky_jpg, "img": "stale", **img_peek}
        res = await testing_client_manager.peek_room(ip, rooms[0]["id"], u0wsc, u0_kr, u0_kr, options=1, part_count=0)
        assert res == {"img0": sky_jpg, "img": "stale", **img_peek}
        res = await testing_client_manager.peek_room(ip, rooms[0]["id"], u0wsc, u0_kr, u0_kr, options=5, part_count=0)
        assert res == {"img0": sky_jpg, "img": "stale", "ps": flame_jpg_size, **img_peek}
        res = await testing_client_manager.peek_room(ip, rooms[0]["id"], u0wsc, u0_kr, u0_kr, options=0, part_count=0)
        assert res == img_peek
        res, preview = await testing_client_manager.peek_room(
            ip, rooms[0]["id"], u0wsc, u0_kr, u0_kr, options=2, part_count=int(len(flame_jpg) / 500_000) + 1
        )
        assert preview == flame_jpg
        assert res == {"img": "stale", **img_peek}
        res, preview = await testing_client_manager.peek_room(
            ip, rooms[0]["id"], u0wsc, u0_kr, u0_kr, options=3, part_count=int(len(flame_jpg) / 500_000) + 1
        )
        assert preview == flame_jpg
        assert res == {"img0": sky_jpg, "img": "stale", **img_peek}
        res, preview = await testing_client_manager.peek_room(
            ip, rooms[0]["id"], u0wsc, u0_kr, u0_kr, options=7, part_count=int(len(flame_jpg) / 500_000) + 1
        )
        assert preview == flame_jpg
        assert res == {"img0": sky_jpg, "img": "stale", "ps": flame_jpg_size, **img_peek}

        # root-peek returns dummy/empty
        res = await testing_client_manager.peek_room("", rooms[0]["id"], u0wsc, u0_kr, u0_kr, part_count=0)
        assert res == {"name": "", "mtime": 0.0}
        res = await testing_client_manager.peek_room("", rooms[0]["id"], u0wsc, u0_kr, u0_kr, options=8, part_count=0)
        assert isinstance(res.pop("total", None), float)
        assert isinstance(res.pop("free", None), float)
        assert res == {"name": "", "mtime": 0.0, "drives": drive_hook_info}

        for p in [None, [""], 0]:
            await testing_client_manager.peek_room(
                p, rooms[0]["id"], u0wsc, u0_kr, u0_kr, error=NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST
            )
        for p in ["..", "/tmp", "_neve. exist where", "_neve. exist where/even less"]:
            await testing_client_manager.peek_room(
                p, rooms[0]["id"], u0wsc, u0_kr, u0_kr, error=NANO_REQUEST_ERROR_HANDLER_TARGET_NOT_FOUND
            )

        aep, aea = get_platform_alternative_acute_e()

        # FS-THMB
        assert (
            await testing_client_manager.thumbnails_room("", 1, rooms[0]["id"], u0wsc, u0_kr, u0_kr, part_count=0) == []
        )
        assert await testing_client_manager.thumbnails_room(
            "large", 1, rooms[0]["id"], u0wsc, u0_kr, u0_kr, part_count=1
        ) == [
            [img_file.name, sky_jpg, (img_peek["mtime"] - 13)],
        ]
        # adhoc
        for p in ["..", "/tmp", "_neve. exist where"]:
            await testing_client_manager.thumbnails_room(
                p,
                1,
                r0_id,
                u0wsc,
                u0_kr,
                u0_kr,
                error=NANO_REQUEST_ERROR_HANDLER_TARGET_NOT_FOUND,
                part_count=1,
                adhoc_token=large_aht,
                adhoc_kr=large_aht_kr,  # NOTE: adhoc kr is not used for owner
            )
        await testing_client_manager.thumbnails_room(
            "",
            1,
            r0_id,
            u0wsc,
            u0_kr,
            u0_kr,
            error=NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST,
            part_count=1,
            adhoc_token=image_aht,
        )
        assert await testing_client_manager.thumbnails_room(
            "", 1, r0_id, u0wsc, u0_kr, u0_kr, part_count=1, adhoc_token=large_aht
        ) == [
            [img_file.name, sky_jpg, (img_peek["mtime"] - 13)],
        ]
        assert (
            await testing_client_manager.thumbnails_room(
                "s", 1, r0_id, u0wsc, u0_kr, u0_kr, part_count=0, adhoc_token=large_aht
            )
            == []
        )
        # multipart test with bucketing
        b_thumbs = await testing_client_manager.all_thumbnails_room("thumbs", rooms[0]["id"], u0wsc, u0_kr, u0_kr)
        assert len(b_thumbs) > 1  # multiple buckets
        thumbs = list(itertools.chain.from_iterable(b_thumbs.values()))
        thumbs.sort(key=lambda x: x[0])
        assert len(thumbs) == thumbs_n
        assert [t[0] for t in thumbs] == [f"{i:04d}.png" for i in range(thumbs_n)]
        assert all(t[1] == sky_jpg for t in thumbs)
        b_thumbs2 = await testing_client_manager.all_thumbnails_room("thumbs", rooms[0]["id"], u0wsc, u0_kr, u0_kr)
        assert b_thumbs2 == b_thumbs
        # test cache punch-through
        (thumbs_dir / thumbs[-11][0]).unlink()
        await asyncio.sleep(1.6)  # event aggregate time wait * ~2
        await run_check_update(session, u_url, auth_h, {})  # commit
        b_thumbs3 = await testing_client_manager.all_thumbnails_room("thumbs", rooms[0]["id"], u0wsc, u0_kr, u0_kr)
        thumbs3 = list(itertools.chain.from_iterable(b_thumbs3.values()))
        assert len(thumbs3) == thumbs_n - 1

        for p in [None, [""], ip, fp]:
            await testing_client_manager.thumbnails_room(
                p, 1, rooms[0]["id"], u0wsc, u0_kr, u0_kr, error=NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST
            )
        for p in ["..", "/tmp", "_neve. exist where", "_neve. exist where/even less"]:
            await testing_client_manager.thumbnails_room(
                p, 1, rooms[0]["id"], u0wsc, u0_kr, u0_kr, error=NANO_REQUEST_ERROR_HANDLER_TARGET_NOT_FOUND
            )

        # fix stale + peek
        ip_upd["mtime"] = img_file.stat().st_mtime_ns
        await run_check_update(session, u_url, auth_h, [ip_upd])
        res = await testing_client_manager.peek_room(ip, rooms[0]["id"], u0wsc, u0_kr, u0_kr, part_count=0)
        assert res == {"img0": sky_jpg, **img_peek}

        # FS-GET part 2
        # large list
        def _write_large_dir():
            for i in range(17323):
                (large_dir / f"{base64.urlsafe_b64encode(os.urandom(147)).decode('ascii')}-{i}.txt").write_text(str(i))

        await asyncio.get_running_loop().run_in_executor(None, _write_large_dir)
        ls_allowed = set(d.name for d in large_dir.iterdir())
        await testing_client_manager.listing_room(
            "large", rooms[0]["id"], u0wsc, u0_kr, u0_kr, ls_path=large_dir, ls_allowed=ls_allowed
        )
        await testing_client_manager.listing_room(
            "large", rooms[0]["id"], u1wsc, u1_data["_acc_kr"], u0_kr, r0_kr, ls_path=large_dir, ls_allowed=ls_allowed
        )
        await testing_client_manager.listing_room(
            "large", rooms[0]["id"], u2wsc, u2_data["_acc_kr"], u0_kr, r0_kr, ls_path=large_dir, ls_allowed=ls_allowed
        )
        await testing_client_manager.listing_room(
            "large",
            rooms[0]["id"],
            anon_wsc,
            anon_kr,
            u0_kr,
            r0_kr,
            ls_path=large_dir,
            ls_allowed=ls_allowed,
            ephemeral_pk=anon_kr.get_public_key(anon_kr.CURVE25519),
        )

        # ADHOC part 2
        large1_dir = r1 / "large"
        large1_dir.mkdir()
        large1_file = large1_dir / "any.txt"
        large1_file.write_bytes(b"xyz012")
        large1_aht, large1_aht_kr = await testing_client_manager.create_adhoc_fs(
            "large", rooms[1]["id"], "adhoc-u0", u0wsc, u0_kr, u0_kr
        )
        await testing_client_manager.peek_room(  # no cross-room token validation
            "",
            rooms[1]["id"],
            anon_wsc,
            anon_kr,
            u0_kr,
            # NOTE: no room keyring rooms[1]["_room_kr"],
            adhoc_kr=large_aht_kr,
            adhoc_token=large_aht,
            ephemeral_pk=anon_kr.get_public_key(anon_kr.CURVE25519),
            error=NANO_REQUEST_ERROR_ADHOC_INVALID,
        )
        assert await testing_client_manager.peek_room(  # adhoc token grants access despite negated allow-anonymous
            "",
            rooms[1]["id"],
            anon_wsc,
            anon_kr,
            u0_kr,
            # NOTE: no room keyring rooms[1]["_room_kr"],
            adhoc_kr=large1_aht_kr,
            adhoc_token=large1_aht,
            ephemeral_pk=anon_kr.get_public_key(anon_kr.CURVE25519),
            part_count=0,
        ) == {
            "name": "large",
            "mtime": large1_dir.stat().st_mtime,
        }

        # FS-MKD
        prefix = "x_"
        for ps, err in [
            ([".", "..", "x\x00x"], NANO_REQUEST_ERROR_INVALID_NEW_NAME),
            ([None, [""], "new_d/newest_d", "/tmp"], NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST),
        ]:
            for p in ps:
                await testing_client_manager.mkdir_room(p, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr, error=err)
        # create dir in root
        p = f"{prefix}new_{aep}d"
        p_alt = f"{prefix}new_{aea}d"
        new_dir = r0 / p
        assert not new_dir.exists()
        assert not (r0 / p_alt).exists()
        resp = await testing_client_manager.mkdir_room(p_alt, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr)
        assert resp.get("norm_name") == p, f"{p} {resp}"
        assert new_dir.is_dir()
        if sys.platform.startswith("darwin"):
            # darwin HFS normalizes composition, the two formats will refer to the same path
            assert (r0 / p_alt).is_dir()
        else:
            assert not (r0 / p_alt).exists()
        await testing_client_manager.mkdir_room(
            p, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr, error=NANO_REQUEST_ERROR_HANDLER_TARGET_EXISTS
        )
        # create file & check conflicts on it
        new_file = new_dir / f"{prefix}new_f"
        new_file.write_text("yes")
        await testing_client_manager.mkdir_room(
            f"{p}/{prefix}new_f", r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr, error=NANO_REQUEST_ERROR_HANDLER_TARGET_EXISTS
        )
        await testing_client_manager.mkdir_room(
            f"{p}/{prefix}new_f/xy", r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr, error=NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST
        )
        # create subdir
        new_sub_dir = new_dir / f"{prefix}newest_d"
        assert not new_sub_dir.exists()
        await testing_client_manager.mkdir_room(p := f"{p}/{prefix}newest_d", r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr)
        assert new_sub_dir.is_dir()
        await testing_client_manager.mkdir_room(
            p, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr, error=NANO_REQUEST_ERROR_HANDLER_TARGET_EXISTS
        )
        # mkdir with upload-only permission
        p = f"{prefix}new_upload_only_d"
        await testing_client_manager.mkdir_room(p, r0_id, "mkd-d0", d0wsc, d0_data["_acc_kr"], u0_kr, r0_kr)
        # deny unauthorized
        p = "just-another-dir"
        await testing_client_manager.mkdir_room(
            p, r0_id, "mkd-u1", u1wsc, u1_data["_acc_kr"], u0_kr, r0_kr, error=NANO_REQUEST_ERROR_HANDLER_DENIED
        )
        await testing_client_manager.mkdir_room(
            p, r0_id, "mkd-u2", u2wsc, u2_data["_acc_kr"], u0_kr, r0_kr, error=NANO_REQUEST_ERROR_HANDLER_DENIED
        )
        # symlink-party
        for sym_type in [sym_broken_contained, sym_broken_leaves, sym_contained, sym_leaves, sym_cycle]:
            await testing_client_manager.mkdir_room(
                sym_type[0].name, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr, error=NANO_REQUEST_ERROR_HANDLER_TARGET_EXISTS
            )

        # FS-DEL
        for p in [None, [""], ".", "..", "/tmp", "new_d/newest_d", "../not_anything"]:
            await testing_client_manager.del_room(
                p, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr, error=NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST
            )
        # test delete simple file
        del_f0 = new_dir / f"{prefix}f_me"
        del_f0.write_text("fme")
        assert del_f0.is_file()
        await testing_client_manager.del_room(f"{new_dir.name}/{prefix}f_me", r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr)
        assert not del_f0.exists()
        # test delete empty dir
        del_d0 = new_dir / f"{prefix}del_d0"
        del_d0.mkdir()
        assert del_d0.is_dir()
        await testing_client_manager.del_room(f"{new_dir.name}/{prefix}del_d0", r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr)
        assert not del_d0.exists()
        # test delete non-empty dir
        del_d0.mkdir()
        (del_d0 / "sd").mkdir()
        (del_d0 / "sf").write_text("sf")
        assert del_d0.is_dir()
        await testing_client_manager.del_room(f"{new_dir.name}/{prefix}del_d0", r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr)
        assert not del_d0.exists()
        # test delete links directly
        del_d0.mkdir()
        del_sd = del_d0 / "sd"
        del_sd.mkdir()
        del_sf = del_d0 / "sf"
        del_sf.write_text("sf")
        del_ln = r0 / f"{prefix}del_ln"
        # test delete direct link to file
        del_ln.symlink_to(del_sf)
        assert del_ln.is_symlink()
        assert del_ln.is_file()
        assert del_sf.is_file()
        await testing_client_manager.del_room(f"{del_ln.name}", r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr)
        assert not del_ln.exists()
        assert del_sf.is_file()
        # test delete direct link to directory
        del_ln.symlink_to(del_d0)
        assert del_ln.is_symlink()
        assert del_ln.is_dir()
        assert del_d0.is_dir()
        assert del_sf.is_file()
        await testing_client_manager.del_room(f"{del_ln.name}", r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr)
        assert not del_ln.exists()
        assert del_d0.is_dir()
        assert del_sf.is_file()
        # test delete folder with symlink inside and keep target
        del_ln = del_sd / f"{prefix}del_ln"
        del_ln.symlink_to(del_sf)
        assert del_ln.is_symlink()
        assert del_ln.is_file()
        assert del_sd.is_dir()
        assert del_sf.is_file()
        await testing_client_manager.del_room(
            f"{new_dir.name}/{del_d0.name}/{del_sd.name}", r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr
        )
        assert not del_ln.exists()
        assert not del_sd.exists()
        assert del_sf.is_file()
        # deny unauthorized
        p = f"{new_dir.name}"
        await testing_client_manager.del_room(
            p, r0_id, "mkd-u1", u1wsc, u1_data["_acc_kr"], u0_kr, r0_kr, error=NANO_REQUEST_ERROR_HANDLER_DENIED
        )
        await testing_client_manager.del_room(
            p, r0_id, "mkd-u2", u2wsc, u2_data["_acc_kr"], u0_kr, r0_kr, error=NANO_REQUEST_ERROR_HANDLER_DENIED
        )
        await testing_client_manager.del_room(
            p, r0_id, "mkd-d0", d0wsc, d0_data["_acc_kr"], u0_kr, r0_kr, error=NANO_REQUEST_ERROR_HANDLER_DENIED
        )
        # symlink-party
        for sym_type in [sym_broken_contained, sym_broken_leaves, sym_contained, sym_leaves, sym_cycle]:
            await testing_client_manager.del_room(sym_type[1].name, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr)

        # FS-PUT
        # check errors
        correct = {
            "path": prefix + "just_put_in_root",
            "size": 5000,
            "mtime": 0,
            "offset": 0,
            "data": os.urandom(100),
        }
        for err_t in [
            {"path": None},
            {"path": [""]},
            {"path": "..", "err": NANO_REQUEST_ERROR_INVALID_NEW_NAME},
            {"path": "x\x00x", "err": NANO_REQUEST_ERROR_INVALID_NEW_NAME},
            {"path": "/tmp"},
            {"size": "10MB"},
            {"size": -500},
            {"mtime": "yesterday"},
            {"mtime": -500},
            {"offset": "start"},
            {"offset": -1},
            {"data": "yes"},
            {"data": 0},
            {"smac": "a" * 16},
            {"smac": b"a" * 15},
            {"smac": 0},
            {"overwrite": "a"},
            {"overwrite": b"a"},
        ]:
            req = dict(correct)
            err = err_t.pop("err", NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST)
            req.update(err_t)
            await testing_client_manager.put_room(req, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr, error=err)
        # put single part file
        await testing_client_manager.put_room(
            dict(
                path=(path := prefix + "put_in_root0"),
                size=-1,
                mtime=-1,
                mtfrac=10498177,  # n/a because mtime is not exact
                offset=0,
                data=(data := os.urandom(100)),
                smac=testing_client_manager.calc_smac([data]),
            ),
            r0_id,
            "mkd-u0",
            u0wsc,
            u0_kr,
            u0_kr,
        )
        assert (r0 / path).read_bytes() == data
        assert abs((r0 / path).lstat().st_mtime - time.time()) < 10.0
        assert is_remote_security_set(r0 / path) is True
        # put single part file with apply-mtime
        await testing_client_manager.put_room(
            dict(
                path=(path := prefix + "put_in_root0_2"),
                size=100,
                mtime=1732634324049,
                mtfrac=10498177,
                offset=0,
                data=(data := os.urandom(100)),
                smac=testing_client_manager.calc_smac([data]),
            ),
            r0_id,
            "mkd-u0",
            u0wsc,
            u0_kr,
            u0_kr,
        )
        assert (r0 / path).read_bytes() == data
        assert (r0 / path).lstat().st_mtime_ns == 1732634324049817700
        assert is_remote_security_set(r0 / path) is True
        # put empty file
        await testing_client_manager.put_room(
            dict(
                path=(path := prefix + "put_in_root0_empty"),
                size=0,
                mtime=-1,
                offset=0,
                data=(data := b""),
                smac=testing_client_manager.calc_smac([data]),
            ),
            r0_id,
            "mkd-u0",
            u0wsc,
            u0_kr,
            u0_kr,
        )
        assert (r0 / path).read_bytes() == data
        assert abs((r0 / path).lstat().st_mtime - time.time()) < 10.0
        assert is_remote_security_set(r0 / path) is True
        # start error (no tracking)
        await testing_client_manager.put_room(
            dict(
                path=(path := prefix + "put_in_root1"),
                size=-1,
                mtime=-1,
                offset=500,
                data=os.urandom(100),
            ),
            r0_id,
            "mkd-u0",
            u0wsc,
            u0_kr,
            u0_kr,
            error=NANO_REQUEST_ERROR_HANDLER_RESET_REQUEST,
        )
        # write first part for multi part integrity errors
        await testing_client_manager.put_room(
            dict(path=path, size=-1, mtime=-1, offset=0, data=os.urandom(100)),
            r0_id,
            "mkd-u0",
            u0wsc,
            u0_kr,
            u0_kr,
        )
        await testing_client_manager.put_room(
            dict(
                path=path,
                size=120,  # reset by size
                mtime=-1,
                offset=100,
                data=os.urandom(100),
            ),
            r0_id,
            "mkd-u0",
            u0wsc,
            u0_kr,
            u0_kr,
            error=NANO_REQUEST_ERROR_HANDLER_RESET_REQUEST,
        )
        await testing_client_manager.put_room(
            dict(
                path=path,
                size=-1,
                mtime=999999,  # reset by size
                offset=100,
                data=os.urandom(100),
            ),
            r0_id,
            "mkd-u0",
            u0wsc,
            u0_kr,
            u0_kr,
            error=NANO_REQUEST_ERROR_HANDLER_RESET_REQUEST,
        )
        # reset existing target
        await testing_client_manager.put_room(
            dict(path=path, size=-1, mtime=-1, offset=0, data=os.urandom(100)),
            r0_id,
            "mkd-u0",
            u0wsc,
            u0_kr,
            u0_kr,
        )
        # part gap integrity error
        await testing_client_manager.put_room(
            dict(path=path, size=-1, mtime=-1, offset=200, data=os.urandom(100), smac=b"0" * 16),
            r0_id,
            "mkd-u0",
            u0wsc,
            u0_kr,
            u0_kr,
            error=NANO_REQUEST_ERROR_HANDLER_RESET_REQUEST,
        )
        # reset existing target 2
        await testing_client_manager.put_room(
            dict(path=path, size=-1, mtime=-1, offset=0, data=os.urandom(100)),
            r0_id,
            "mkd-u0",
            u0wsc,
            u0_kr,
            u0_kr,
        )
        # sum mac integrity error
        await testing_client_manager.put_room(
            dict(path=path, size=-1, mtime=-1, offset=100, data=os.urandom(100), smac=b"0" * 16),
            r0_id,
            "mkd-u0",
            u0wsc,
            u0_kr,
            u0_kr,
            error=NANO_REQUEST_ERROR_HANDLER_RESET_REQUEST,
        )
        # multi part large upload
        path = prefix + f"put_in_{aep}_root2"
        path_alt = prefix + f"put_in_{aea}_root2"
        data_list = []
        offset = 0
        for i in range(7):
            data_list.append(data := os.urandom(1001))
            resp = await testing_client_manager.put_room(
                dict(
                    path=(path_alt if i % 2 == 0 else path),
                    size=7007,
                    mtime=1612089337617,
                    offset=offset,
                    data=data,
                    smac=testing_client_manager.calc_smac(data_list) if i == 6 else None,
                ),
                r0_id,
                "mkd-u0",
                u0wsc,
                u0_kr,
                u0_kr,
            )
            if offset == 0:
                assert resp.get("norm_name") == path, f"{path} {resp}"
            else:
                assert "norm_name" not in resp, str(resp)
            offset += len(data)
        assert (r0 / path).read_bytes() == b"".join(data_list)
        # overwrite error
        for p_ in [path, path_alt]:
            await testing_client_manager.put_room(
                dict(
                    path=p_,
                    size=-1,
                    mtime=-1,
                    offset=0,
                    data=(data := os.urandom(100)),
                    smac=testing_client_manager.calc_smac([data]),
                ),
                r0_id,
                "mkd-u0",
                u0wsc,
                u0_kr,
                u0_kr,
                error=NANO_REQUEST_ERROR_HANDLER_TARGET_EXISTS,
            )
        assert (r0 / path).read_bytes() == b"".join(data_list)
        resp = await testing_client_manager.put_room(
            dict(
                path=path,
                size=-1,
                mtime=-1,
                offset=0,
                data=(data := os.urandom(100)),
                smac=testing_client_manager.calc_smac([data]),
                overwrite=True,
            ),
            r0_id,
            "mkd-u0",
            u0wsc,
            u0_kr,
            u0_kr,
        )
        assert resp.get("norm_name") == path, f"{path} {resp}"
        assert (r0 / path).read_bytes() == data
        if sys.platform.startswith("darwin"):
            # darwin HFS normalizes composition, the two formats will refer to the same path
            assert (r0 / path_alt).read_bytes() == data
        else:
            assert not (r0 / path_alt).exists()
        # not existent path
        await testing_client_manager.put_room(
            dict(
                path="something/that/does/not/exist",
                size=-1,
                mtime=-1,
                offset=0,
                data=(data := os.urandom(100)),
                smac=testing_client_manager.calc_smac([data]),
            ),
            r0_id,
            "mkd-u0",
            u0wsc,
            u0_kr,
            u0_kr,
            error=NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST,
        )
        # existing
        path = prefix + "put_in_root3"
        (r0 / path).mkdir()
        await testing_client_manager.put_room(
            dict(
                path=path,
                size=-1,
                mtime=-1,
                offset=0,
                data=(data := os.urandom(100)),
                smac=testing_client_manager.calc_smac([data]),
            ),
            r0_id,
            "mkd-u0",
            u0wsc,
            u0_kr,
            u0_kr,
            error=NANO_REQUEST_ERROR_HANDLER_TARGET_EXISTS,
        )
        # deny unauthorized
        await testing_client_manager.put_room(
            dict(
                path=prefix + "put_in_root_xkl",
                size=-1,
                mtime=-1,
                offset=0,
                data=(data := os.urandom(100)),
                smac=testing_client_manager.calc_smac([data]),
            ),
            r0_id,
            "mkd-u1",
            u1wsc,
            u1_data["_acc_kr"],
            u0_kr,
            r0_kr,
            error=NANO_REQUEST_ERROR_HANDLER_DENIED,
        )
        await testing_client_manager.put_room(
            dict(
                path=prefix + "put_in_root_xkl",
                size=-1,
                mtime=-1,
                offset=0,
                data=(data := os.urandom(100)),
                smac=testing_client_manager.calc_smac([data]),
            ),
            r0_id,
            "mkd-u2",
            u2wsc,
            u2_data["_acc_kr"],
            u0_kr,
            r0_kr,
            error=NANO_REQUEST_ERROR_HANDLER_DENIED,
        )
        path_perm0 = prefix + "put_perm_t0"
        path_perm1 = prefix + "put_perm_t1"
        await testing_client_manager.put_room(
            dict(
                path=path_perm0,
                size=-1,
                mtime=-1,
                offset=0,
                data=(data0 := os.urandom(100)),
                smac=testing_client_manager.calc_smac([data0]),
            ),
            r0_id,
            "mkd-u3",
            u3wsc,
            u3_data["_acc_kr"],
            u0_kr,
            r0_kr,
        )
        await testing_client_manager.put_room(
            dict(
                path=path_perm1,
                size=-1,
                mtime=-1,
                offset=0,
                data=(data1 := os.urandom(100)),
                smac=testing_client_manager.calc_smac([data1]),
            ),
            r0_id,
            "mkd-d0",
            d0wsc,
            d0_data["_acc_kr"],
            u0_kr,
            r0_kr,
        )
        assert (r0 / path_perm0).read_bytes() == data0
        assert (r0 / path_perm1).read_bytes() == data1
        await testing_client_manager.put_room(
            dict(
                path=path_perm0,
                size=-1,
                mtime=-1,
                offset=0,
                data=(data := os.urandom(100)),
                smac=testing_client_manager.calc_smac([data]),
            ),
            r0_id,
            "mkd-d0",
            d0wsc,
            d0_data["_acc_kr"],
            u0_kr,
            r0_kr,
            error=NANO_REQUEST_ERROR_HANDLER_DENIED,
        )
        await testing_client_manager.put_room(
            dict(
                path=path_perm0,
                size=-1,
                mtime=-1,
                offset=0,
                data=(data := os.urandom(100)),
                smac=testing_client_manager.calc_smac([data]),
                overwrite=True,
            ),
            r0_id,
            "mkd-d0",
            d0wsc,
            d0_data["_acc_kr"],
            u0_kr,
            r0_kr,
            error=NANO_REQUEST_ERROR_HANDLER_DENIED,
        )
        assert (r0 / path_perm0).read_bytes() == data0
        assert (r0 / path_perm1).read_bytes() == data1
        await testing_client_manager.put_room(
            dict(
                path=path_perm1,
                size=-1,
                mtime=-1,
                offset=0,
                data=(data1 := os.urandom(100)),
                smac=testing_client_manager.calc_smac([data1]),
                overwrite=True,
            ),
            r0_id,
            "mkd-u3",
            u3wsc,
            u3_data["_acc_kr"],
            u0_kr,
            r0_kr,
        )
        assert (r0 / path_perm0).read_bytes() == data0
        assert (r0 / path_perm1).read_bytes() == data1
        # symlink-party
        for sym_type in [sym_broken_contained, sym_broken_leaves, sym_contained, sym_leaves, sym_cycle]:
            await testing_client_manager.put_room(
                dict(
                    path=sym_type[0].name,
                    size=-1,
                    mtime=-1,
                    offset=0,
                    data=(data := os.urandom(100)),
                    smac=testing_client_manager.calc_smac([data]),
                ),
                r0_id,
                "mkd-u0",
                u0wsc,
                u0_kr,
                u0_kr,
                error=NANO_REQUEST_ERROR_HANDLER_TARGET_EXISTS,
            )

        # PUT with auto-name
        assert not (r0 / "Nano").exists()
        auto_name = "something.dat"
        req = dict(auto_name=55)
        await testing_client_manager.put_room(
            req, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr, error=NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST
        )
        req.update(dict(auto_name="a/b"))
        await testing_client_manager.put_room(
            req, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr, error=NANO_REQUEST_ERROR_INVALID_NEW_NAME
        )
        # test auto-name "no-op" with preferred path
        req.update(dict(auto_name=auto_name, path="auto-root.dat", offset=0, size=-1, mtime=-1))
        req.update(dict(data=(auto_data0 := os.urandom(100)), smac=testing_client_manager.calc_smac([auto_data0])))
        resp = await testing_client_manager.put_room(req, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr)
        assert resp["norm_path"] == "auto-root.dat"
        assert (r0 / resp["norm_path"]).read_bytes() == auto_data0
        # test auto-name final-change with preferred path
        req.update(dict(data=(auto_data1 := os.urandom(100)), smac=testing_client_manager.calc_smac([auto_data1])))
        resp = await testing_client_manager.put_room(req, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr)
        assert resp["norm_path"].startswith("something-")
        assert (r0 / resp["norm_path"]).read_bytes() == auto_data1
        # test auto-selecting location
        assert not (r0 / "Nano").exists()
        req.pop("path")
        req.update(dict(data=(auto_data2 := os.urandom(100)), smac=testing_client_manager.calc_smac([auto_data2])))
        resp = await testing_client_manager.put_room(req, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr)
        assert resp["norm_path"].startswith("Nano/")
        assert resp["norm_name"] and resp["norm_path"].endswith("/" + resp["norm_name"])
        assert (r0 / resp["norm_path"]).read_bytes() == auto_data2
        # test put empty file with auto-name
        req.update(
            dict(
                path="auto-empty.dat",
                auto_name="auto-empty.dat",
                data=b"",
                smac=testing_client_manager.calc_smac([b""]),
            )
        )
        resp = await testing_client_manager.put_room(req, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr)
        assert resp["norm_path"] == "auto-empty.dat"
        assert (r0 / resp["norm_path"]).read_bytes() == b""
        resp = await testing_client_manager.put_room(req, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr)
        assert resp["norm_path"].startswith("auto-empty-")
        assert (r0 / resp["norm_path"]).read_bytes() == b""
        # test multi-part upload (reset and conflict at finalize)
        req.pop("path")
        req.pop("smac")
        req.update(dict(auto_name=auto_name, data=os.urandom(100), offset=0))
        resp = await testing_client_manager.put_room(req, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr)
        req.update(dict(data=os.urandom(100), offset=100, path=resp["norm_path"]))
        await testing_client_manager.put_room(req, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr)
        req.update(dict(data=(auto_data3 := os.urandom(100)), offset=0))  # reset
        resp = await testing_client_manager.put_room(req, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr)
        assert "norm_path" not in resp, str(resp)
        assert resp["norm_name"] and req["path"].endswith("/" + resp["norm_name"]), f"{req}\n{resp}"
        req.update(dict(data=(auto_data4 := os.urandom(100)), offset=100))
        await testing_client_manager.put_room(req, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr)
        (r0 / req["path"]).write_bytes(conflict_data0 := os.urandom(50))
        req.update(
            dict(
                data=(auto_data5 := os.urandom(100)),
                offset=200,
                smac=testing_client_manager.calc_smac([auto_data3, auto_data4, auto_data5]),
            )
        )
        resp = await testing_client_manager.put_room(req, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr)
        assert "norm_name" not in resp, str(resp)
        assert req["path"] != resp["norm_path"], f"{req}\n{resp}"
        assert (r0 / resp["norm_path"]).read_bytes() == auto_data3 + auto_data4 + auto_data5
        assert (r0 / req["path"]).read_bytes() == conflict_data0
        # test fixing "Nano" being a file
        (r0 / "Nano").rename((r0 / "First-Nano"))
        (r0 / "Nano").write_bytes(conflict_data1 := os.urandom(50))
        req.pop("path")
        req.update(
            dict(data=(auto_data6 := os.urandom(100)), offset=0, smac=testing_client_manager.calc_smac([auto_data6]))
        )
        resp = await testing_client_manager.put_room(req, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr)
        assert (r0 / resp["norm_path"]).read_bytes() == auto_data6
        assert (r0 / "Nano").is_dir()
        moved = [i for i in r0.iterdir() if i.name.startswith("Nano-")]
        assert len(moved) == 1, str(moved)
        assert moved[0].read_bytes() == conflict_data1
        moved[0].rename(r0 / "Second-Conf")
        # test fixing "Nano" being a broken symlink
        (r0 / "Nano").rename((r0 / "Second-Nano"))
        (r0 / "Nano").symlink_to(conflict_link0 := "_something_something_no real_yes", target_is_directory=True)
        req.update(dict(data=(auto_data7 := os.urandom(100)), smac=testing_client_manager.calc_smac([auto_data7])))
        resp = await testing_client_manager.put_room(req, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr)
        assert (r0 / resp["norm_path"]).read_bytes() == auto_data7
        assert (r0 / "Nano").is_dir()
        moved = [i for i in r0.iterdir() if i.name.startswith("Nano-")]
        assert len(moved) == 1, str(moved)
        assert os.readlink(moved[0]) == conflict_link0
        # test fixing "date-dir" being a file
        tmp = list((r0 / "Nano").iterdir())
        assert len(tmp) == 1
        date_dir = tmp[0]
        date_dir.rename(date_dir.parent / f"First-{date_dir.name}")
        date_dir.write_bytes(conflict_data2 := os.urandom(50))
        req.update(
            dict(data=(auto_data8 := os.urandom(100)), offset=0, smac=testing_client_manager.calc_smac([auto_data8]))
        )
        resp = await testing_client_manager.put_room(req, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr)
        assert (r0 / resp["norm_path"]).read_bytes() == auto_data8
        assert (r0 / "Nano").is_dir()
        assert date_dir.is_dir()
        moved = [i for i in date_dir.parent.iterdir() if i.name.startswith(f"{date_dir.name}-")]
        assert len(moved) == 1, str(moved)
        assert moved[0].read_bytes() == conflict_data2
        moved[0].rename(date_dir.parent / "First-Conf")
        # test fixing "date-dir" being a broken symlink
        date_dir.rename(date_dir.parent / f"Second-{date_dir.name}")
        date_dir.symlink_to(conflict_link1 := "_something_something_no real_no", target_is_directory=True)
        req.update(dict(data=(auto_data9 := os.urandom(100)), smac=testing_client_manager.calc_smac([auto_data9])))
        resp = await testing_client_manager.put_room(req, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr)
        assert (r0 / resp["norm_path"]).read_bytes() == auto_data9
        assert (r0 / "Nano").is_dir()
        assert date_dir.is_dir()
        moved = [i for i in date_dir.parent.iterdir() if i.name.startswith(f"{date_dir.name}-")]
        assert len(moved) == 1, str(moved)
        assert os.readlink(moved[0]) == conflict_link1
        # test using an alternate location by having a "Nano" as symlink
        (r0 / "Nano").rename((r0 / "Somewhere-Else-Nano"))
        (r0 / "Nano").symlink_to("Somewhere-Else-Nano", target_is_directory=True)
        req.update(dict(data=(auto_data10 := os.urandom(100)), smac=testing_client_manager.calc_smac([auto_data10])))
        resp = await testing_client_manager.put_room(req, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr)
        assert (r0 / resp["norm_path"]).read_bytes() == auto_data10
        assert (r0 / "Nano").is_symlink()
        assert resp["norm_path"].startswith(f"Somewhere-Else-Nano/{date_dir.name}/")

        # FS-MOV
        prefix = "mx_"
        mov_f0 = r0 / f"{prefix}f_0"
        mov_f0.write_text("f_0")
        mov_f1 = r0 / f"{prefix}f_1"
        assert mov_f0.is_file()
        assert not mov_f1.exists()
        # either not str -> bad-req from init
        for sp, tp in itertools.product([mov_f0.name], [None, [""]]):
            await testing_client_manager.move_room(
                sp, tp, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr, error=NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST
            )
            await testing_client_manager.move_room(
                tp, sp, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr, error=NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST
            )
        # invalid path for target -> often INVALID_NEW_NAME
        for ps, err in [
            ([".", "..", "x\x00x", "xy/\x00"], NANO_REQUEST_ERROR_INVALID_NEW_NAME),
            (["/tmp", "../not_anything"], NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST),
        ]:
            for p in ps:
                await testing_client_manager.move_room(mov_f0.name, p, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr, error=err)
        # invalid path for src -> BAD_REQ
        for p in [".", "..", "x\x00x", "/tmp", "../not_anything"]:
            await testing_client_manager.move_room(
                p, mov_f1.name, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr, error=NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST
            )
        # f0 exists, f1 does not
        f0n = mov_f0.name
        f1n = mov_f1.name
        ddn = "new_d/newest_d"
        await testing_client_manager.move_room(
            f1n, f0n, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr, error=NANO_REQUEST_ERROR_HANDLER_TARGET_NOT_FOUND
        )
        await testing_client_manager.move_room(
            f1n, ddn, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr, error=NANO_REQUEST_ERROR_HANDLER_TARGET_NOT_FOUND
        )
        await testing_client_manager.move_room(
            f0n, ddn, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr, error=NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST
        )
        await testing_client_manager.move_room(
            f1n, ddn, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr, error=NANO_REQUEST_ERROR_HANDLER_TARGET_NOT_FOUND
        )
        await testing_client_manager.move_room(
            ddn, f0n, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr, error=NANO_REQUEST_ERROR_HANDLER_TARGET_NOT_FOUND
        )
        await testing_client_manager.move_room(
            ddn, f1n, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr, error=NANO_REQUEST_ERROR_HANDLER_TARGET_NOT_FOUND
        )
        assert mov_f0.is_file()
        assert not mov_f1.exists()
        mov_f2 = r0 / f"{prefix}f_2"
        mov_f2.write_text("f_2")
        await testing_client_manager.move_room(
            mov_f0.name,
            mov_f2.name,
            r0_id,
            "mkd-u0",
            u0wsc,
            u0_kr,
            u0_kr,
            error=NANO_REQUEST_ERROR_HANDLER_TARGET_EXISTS,
        )
        assert mov_f0.read_text() == "f_0"
        assert not mov_f1.exists()
        assert mov_f2.read_text() == "f_2"
        # test move simple file and check remote-security
        if not sys.platform.startswith("win"):
            mov_f0.chmod(0o754)
        assert is_remote_security_set(mov_f0) is False
        await testing_client_manager.move_room(mov_f0.name, mov_f1.name, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr)
        assert not mov_f0.exists()
        assert mov_f1.read_text() == "f_0"
        assert is_remote_security_set(mov_f1) is True
        # test move into and out of directory
        mov_d0 = r0 / f"{prefix}d_0"
        mov_d0.mkdir()
        mov_d00 = mov_d0 / "d0"
        mov_d00.mkdir()
        await testing_client_manager.move_room(
            mov_f1.name,
            mov_d0.name,
            r0_id,
            "mkd-u0",
            u0wsc,
            u0_kr,
            u0_kr,
            error=NANO_REQUEST_ERROR_HANDLER_TARGET_EXISTS,
        )
        await testing_client_manager.move_room(
            mov_f1.name,
            mov_d00.relative_to(r0).as_posix(),
            r0_id,
            "mkd-u0",
            u0wsc,
            u0_kr,
            u0_kr,
            error=NANO_REQUEST_ERROR_HANDLER_TARGET_EXISTS,
        )
        assert not mov_f0.exists()
        assert mov_f1.read_text() == "f_0"
        d00fx = mov_d00.relative_to(r0) / "fx"
        await testing_client_manager.move_room(mov_f1.name, d00fx.as_posix(), r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr)
        assert not mov_f0.exists()
        assert not mov_f1.exists()
        assert (r0 / d00fx).read_text() == "f_0"
        d0fy = mov_d0.relative_to(r0) / "fy"
        await testing_client_manager.move_room(d00fx.as_posix(), d0fy.as_posix(), r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr)
        assert not mov_f0.exists()
        assert not mov_f1.exists()
        assert not (r0 / d00fx).exists()
        assert (r0 / d0fy).read_text() == "f_0"
        # test move directory
        assert mov_d00.is_dir()
        mov_d1 = r0 / f"{prefix}d_1"
        await testing_client_manager.move_room(
            mov_d00.relative_to(r0).as_posix(),
            (mov_d1 / mov_d00.name).relative_to(r0).as_posix(),
            r0_id,
            "mkd-u0",
            u0wsc,
            u0_kr,
            u0_kr,
            error=NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST,
        )
        assert not mov_d1.exists()
        mov_d1.mkdir()
        await testing_client_manager.move_room(
            mov_d00.relative_to(r0).as_posix(),
            (mov_d1 / mov_d00.name).relative_to(r0).as_posix(),
            r0_id,
            "mkd-u0",
            u0wsc,
            u0_kr,
            u0_kr,
        )
        assert mov_d0.is_dir()
        assert not mov_d00.exists()
        assert set(x.name for x in mov_d1.iterdir()) == {mov_d00.name}
        await testing_client_manager.move_room(
            mov_d0.relative_to(r0).as_posix(),
            (mov_d1 / (mov_d0.name + "X")).relative_to(r0).as_posix(),
            r0_id,
            "mkd-u0",
            u0wsc,
            u0_kr,
            u0_kr,
        )
        assert not mov_d0.exists()
        assert not mov_d00.exists()
        assert set(x.name for x in mov_d1.iterdir()) == {mov_d0.name + "X", mov_d00.name}
        assert set(x.name for x in (mov_d1 / (mov_d0.name + "X")).iterdir()) == {"fy"}
        assert (mov_d1 / (mov_d0.name + "X") / "fy").read_text() == "f_0"
        await testing_client_manager.move_room(mov_d1.name, mov_d0.name, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr)
        assert not mov_d1.exists()
        assert mov_d0.is_dir()
        mov_d1.mkdir()
        await testing_client_manager.move_room(
            mov_d0.name,
            mov_d1.name,
            r0_id,
            "mkd-u0",
            u0wsc,
            u0_kr,
            u0_kr,
            error=NANO_REQUEST_ERROR_HANDLER_TARGET_EXISTS,
        )
        assert mov_d0.is_dir()
        assert mov_d1.is_dir()
        assert not list(mov_d1.iterdir())
        # test path name normalization
        uni_d = r0 / f"{prefix}_uni_{aep}"
        uni_d_alt = r0 / f"{prefix}_uni_{aea}"
        assert not uni_d.exists()
        assert not uni_d_alt.exists()
        resp = await testing_client_manager.move_room(mov_d0.name, uni_d_alt.name, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr)
        assert not mov_d0.exists()
        assert resp.get("norm_name") == uni_d.name, f"{uni_d.name} {resp}"
        assert uni_d.is_dir()
        if sys.platform.startswith("darwin"):
            # darwin HFS normalizes composition, the two formats will refer to the same path
            assert uni_d_alt.is_dir()
        else:
            assert not uni_d_alt.exists()
        # test path name case change
        mov_f_case0 = r0 / "f_CasE.tXt"
        mov_f_case1 = r0 / "F_casE.TxT"
        mov_f_case0.write_text("something caseful")
        assert mov_f_case0.name in os.listdir(r0)
        assert mov_f_case1.name not in os.listdir(r0)
        assert mov_f_case0.exists()
        other_case_existed = mov_f_case1.exists()
        await testing_client_manager.move_room(mov_f_case0.name, mov_f_case1.name, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr)
        assert mov_f_case0.name not in os.listdir(r0)
        assert mov_f_case1.name in os.listdir(r0)
        assert mov_f_case0.exists() is other_case_existed  # the alt case name shall consistently exist or not
        assert mov_f_case1.exists()
        if other_case_existed:  # check more case insensitive qualities
            assert os.path.samefile(mov_f_case0, mov_f_case1)
            if sys.platform.startswith("win"):  # realpath only works on windows for retrieving the actual filename
                assert os.path.basename(os.path.realpath(mov_f_case0)) == mov_f_case1.name
            assert mov_f_case0.read_text() == "something caseful"
        assert mov_f_case1.read_text() == "something caseful"
        # deny unauthorized
        p = mov_d0.name
        await testing_client_manager.move_room(
            p, "a", r0_id, "mkd-u1", u1wsc, u1_data["_acc_kr"], u0_kr, r0_kr, error=NANO_REQUEST_ERROR_HANDLER_DENIED
        )
        await testing_client_manager.move_room(
            p, "a", r0_id, "mkd-u2", u2wsc, u2_data["_acc_kr"], u0_kr, r0_kr, error=NANO_REQUEST_ERROR_HANDLER_DENIED
        )
        await testing_client_manager.move_room(
            p, "a", r0_id, "mkd-d0", d0wsc, d0_data["_acc_kr"], u0_kr, r0_kr, error=NANO_REQUEST_ERROR_HANDLER_DENIED
        )
        # symlink-party
        mov_sp_f0 = r0 / f"{prefix}sp_f0"
        mov_sp_f0.write_text("sp_f0")
        mov_sp_d0 = r0 / f"{prefix}sp_d0"
        mov_sp_d0.mkdir()
        mov_sp_d0_f1 = mov_sp_d0 / "f1"
        mov_sp_d0_f1.write_text("sp_d0_f1")
        for sym_type in [sym_broken_contained, sym_broken_leaves, sym_contained, sym_leaves, sym_cycle]:
            await testing_client_manager.move_room(
                mov_sp_f0.name,
                sym_type[0].name,
                r0_id,
                "mkd-u0",
                u0wsc,
                u0_kr,
                u0_kr,
                error=NANO_REQUEST_ERROR_HANDLER_TARGET_EXISTS,
            )
            await testing_client_manager.move_room(
                mov_sp_d0.name,
                sym_type[0].name,
                r0_id,
                "mkd-u0",
                u0wsc,
                u0_kr,
                u0_kr,
                error=NANO_REQUEST_ERROR_HANDLER_TARGET_EXISTS,
            )
            assert mov_sp_f0.is_file()
            assert mov_sp_d0.is_dir()
            assert sym_type[0].is_symlink()
        for sym_type in [sym_broken_contained, sym_broken_leaves, sym_contained, sym_leaves, sym_cycle]:
            moved_lnk = mov_sp_d0 / sym_type[2].name
            assert sym_type[2].is_symlink()
            assert not moved_lnk.exists()
            await testing_client_manager.move_room(
                sym_type[2].name, mov_sp_d0.name + "/" + sym_type[2].name, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr
            )
            assert mov_sp_f0.is_file()
            assert mov_sp_d0.is_dir()
            assert not sym_type[2].exists()
            assert moved_lnk.is_symlink()

        # locking things
        prefix = "lck_"
        lck_f1 = r0 / f"{prefix}f1"
        lck_d0 = r0 / f"{prefix}d0"
        lck_d0.mkdir()
        lck_d0_f0 = lck_d0 / f"{prefix}f0"
        lck_d0_f0.write_bytes(b"lock-0")
        lck_d0_f0_s = lck_d0_f0.relative_to(r0).as_posix()
        lck_d0_f1 = lck_d0 / f"{prefix}f1"
        lck_d0_f1.write_bytes(b"lock-1")
        lck_d0_f1_s = lck_d0_f1.relative_to(r0).as_posix()

        for p in [lck_d0_f0_s, lck_d0_f1_s, lck_d0.name]:
            l = await testing_client_manager.get_lock_room(p, r0_id, u0wsc, u0_kr, u0_kr)
            assert l is None, str(l)
        await testing_client_manager.get_lock_room(
            lck_f1.name, r0_id, u0wsc, u0_kr, u0_kr, error=NANO_REQUEST_ERROR_HANDLER_TARGET_NOT_FOUND
        )
        tk0 = "tk0"
        tk1 = "tk1"
        c = await testing_client_manager.lock_room(lck_d0_f0_s, tk0, "create", r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr)
        assert c is None, str(c)
        for p in [lck_d0_f0_s, lck_d0.name]:
            l = await testing_client_manager.get_lock_room(p, r0_id, u0wsc, u0_kr, u0_kr)
            assert l == tk0, str(l)
        l = await testing_client_manager.get_lock_room(lck_d0_f1_s, r0_id, u0wsc, u0_kr, u0_kr)
        assert l is None, str(l)
        c = await testing_client_manager.lock_room(lck_d0_f0_s, tk1, "create", r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr)
        assert c == tk0, str(c)
        c = await testing_client_manager.lock_room(lck_d0_f0_s, tk1, "delete", r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr)
        assert c == tk0, str(c)
        c = await testing_client_manager.lock_room(lck_d0_f0_s, tk1, "refresh", r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr)
        assert c == tk0, str(c)
        c = await testing_client_manager.lock_room(
            lck_d0_f0_s, tk1, "replace", r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr, old_token="xx"
        )
        assert c == tk0, str(c)
        l = await testing_client_manager.get_lock_room(lck_d0_f0_s, r0_id, u0wsc, u0_kr, u0_kr)
        assert l == tk0, str(l)
        c = await testing_client_manager.lock_room(lck_d0_f0_s, tk0, "create", r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr)
        assert c is None, str(c)
        c = await testing_client_manager.lock_room(lck_d0_f0_s, tk0, "refresh", r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr)
        assert c is None, str(c)
        c = await testing_client_manager.lock_room(
            lck_d0_f0_s, tk1, "replace", r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr, old_token=tk0
        )
        assert c is None, str(c)
        l = await testing_client_manager.get_lock_room(lck_d0_f0_s, r0_id, u0wsc, u0_kr, u0_kr)
        assert l == tk1, str(l)
        await testing_client_manager.del_room(
            lck_d0.name, r0_id, "mkd-u0", u0wsc, u0_kr, u0_kr, error=NANO_REQUEST_ERROR_LOCKED_PATH
        )
        l = await testing_client_manager.get_lock_room(lck_d0.name, r0_id, u0wsc, u0_kr, u0_kr)
        assert l == tk1, str(l)
        c = await testing_client_manager.lock_room(
            lck_d0_f0_s, tk1, "delete", r0_id, "mkd-u3", u3wsc, u3_data["_acc_kr"], u0_kr, r0_kr
        )
        assert c is None, str(c)
        p = lck_d0.name
        await testing_client_manager.get_lock_room(
            p, r0_id, u1wsc, u1_data["_acc_kr"], u0_kr, r0_kr, error=NANO_REQUEST_ERROR_HANDLER_DENIED
        )
        await testing_client_manager.get_lock_room(
            p, r0_id, u2wsc, u2_data["_acc_kr"], u0_kr, r0_kr, error=NANO_REQUEST_ERROR_HANDLER_DENIED
        )
        await testing_client_manager.get_lock_room(
            p, r0_id, d0wsc, d0_data["_acc_kr"], u0_kr, r0_kr, error=NANO_REQUEST_ERROR_HANDLER_DENIED
        )
        await testing_client_manager.get_lock_room(
            p,
            r0_id,
            anon_wsc,
            anon_kr,
            u0_kr,
            r0_kr,
            ephemeral_pk=anon_kr.get_public_key(anon_kr.CURVE25519),
            error=NANO_REQUEST_ERROR_HANDLER_DENIED,
        )
        await testing_client_manager.lock_room(
            p,
            tk0,
            "create",
            r0_id,
            "mkd-u1",
            u1wsc,
            u1_data["_acc_kr"],
            u0_kr,
            r0_kr,
            error=NANO_REQUEST_ERROR_HANDLER_DENIED,
        )
        await testing_client_manager.lock_room(
            p,
            tk0,
            "create",
            r0_id,
            "mkd-u2",
            u2wsc,
            u2_data["_acc_kr"],
            u0_kr,
            r0_kr,
            error=NANO_REQUEST_ERROR_HANDLER_DENIED,
        )
        await testing_client_manager.lock_room(
            p,
            tk0,
            "create",
            r0_id,
            "mkd-d0",
            d0wsc,
            d0_data["_acc_kr"],
            u0_kr,
            r0_kr,
            error=NANO_REQUEST_ERROR_HANDLER_DENIED,
        )

        # check adhoc deny by membership revoke
        res = await testing_client_manager.peek_room(
            "",
            rooms[0]["id"],
            anon_wsc,
            anon_kr,
            u0_kr,
            r0_kr,
            adhoc_kr=image_aht_kr,
            adhoc_token=image_aht,
            ephemeral_pk=anon_kr.get_public_key(anon_kr.CURVE25519),
            part_count=0,
        )
        assert "img0" in res
        await testing_client_manager.nano_request_response(
            msgpack_dumps(
                await testing_client_manager.prepare_request_with_mutation_id(
                    {
                        "type": "ROOM-CFG",
                        "room_id": safe_radix32.encode_fw(rooms[0]["id"]),
                        "member_flags": {
                            safe_radix32.encode_fw(u1_data["account_id"]): GroupConfigBlockMemberFlags.ACTIVE_NEGATE,
                        },
                    },
                    "dbg_fs0",
                    rooms[0]["id"],
                    None,
                    u0wsc,
                    u0_kr,
                    u0_kr,
                )
            ),
            rooms[0]["id"],
            None,
            u0wsc,
            u0_kr,
            u0_kr,
        )
        await testing_client_manager.peek_room(
            "",
            rooms[0]["id"],
            anon_wsc,
            anon_kr,
            u0_kr,
            r0_kr,
            adhoc_kr=image_aht_kr,
            adhoc_token=image_aht,
            ephemeral_pk=anon_kr.get_public_key(anon_kr.CURVE25519),
            part_count=0,
            error=NANO_REQUEST_ERROR_ADHOC_INVALID,
        )
