     1→import base64
     2→
     3→import pytest
     4→
     5→
     6→def test_create_workspace(client):
     7→    resp = client.post("/workspaces", json={"mounts": {"/tmp/": "memory"}})
     8→    assert resp.status_code == 200
     9→    data = resp.json()
    10→    assert "id" in data
    11→
    12→
    13→def test_list_workspaces(client):
    14→    client.post("/workspaces", json={"mounts": {"/tmp/": "memory"}})
    15→    resp = client.get("/workspaces")
    16→    assert resp.status_code == 200
    17→    data = resp.json()
    18→    assert isinstance(data, list)
    19→    assert len(data) == 1
    20→    assert data[0]["fuse_mounted"] is False
    21→    assert data[0]["mountpoint"] is None
    22→
    23→
    24→def test_exec_builtin(client):
    25→    resp = client.post("/workspaces", json={"mounts": {"/tmp/": "memory"}})
    26→    ws_id = resp.json()["id"]
    27→    resp = client.post(f"/workspaces/{ws_id}/exec",
    28→                       json={"command": "ls /tmp/"})
    29→    assert resp.status_code == 200
    30→    assert "output" in resp.json()
    31→
    32→
    33→def test_exec_unknown_workspace(client):
    34→    resp = client.post("/workspaces/bad-id/exec", json={"command": "ls /"})
    35→    assert resp.status_code == 404
    36→
    37→
    38→def test_delete_workspace(client):
    39→    resp = client.post("/workspaces", json={"mounts": {"/tmp/": "memory"}})
    40→    ws_id = resp.json()["id"]
    41→    resp = client.delete(f"/workspaces/{ws_id}")
    42→    assert resp.status_code == 204
    43→
    44→
    45→def test_sync_workspace(client):
    46→    resp = client.post("/workspaces", json={"mounts": {"/tmp/": "memory"}})
    47→    ws_id = resp.json()["id"]
    48→    resp = client.post(f"/workspaces/{ws_id}/sync")
    49→    assert resp.status_code == 200
    50→
    51→
    52→def test_status_workspace(client):
    53→    resp = client.post("/workspaces", json={"mounts": {"/tmp/": "memory"}})
    54→    ws_id = resp.json()["id"]
    55→    resp = client.get(f"/workspaces/{ws_id}/status")
    56→    assert resp.status_code == 200
    57→    assert isinstance(resp.json(), list)
    58→
    59→
    60→def test_register_fuse(client):
    61→    resp = client.post("/workspaces/fuse",
    62→                       json={
    63→                           "mountpoint": "/tmp/ws",
    64→                           "pid": 12345
    65→                       })
    66→    assert resp.status_code == 200
    67→    assert "id" in resp.json()
    68→
    69→
    70→def test_list_includes_fuse(client):
    71→    client.post("/workspaces", json={"mounts": {"/tmp/": "memory"}})
    72→    client.post("/workspaces/fuse",
    73→                json={
    74→                    "mountpoint": "/tmp/ws",
    75→                    "pid": 12345
    76→                })
    77→    resp = client.get("/workspaces")
    78→    data = resp.json()
    79→    assert len(data) == 2
    80→    fuse_values = {d["fuse_mounted"] for d in data}
    81→    assert fuse_values == {False, True}
    82→    fuse_entry = [d for d in data if d["fuse_mounted"]][0]
    83→    assert fuse_entry["mountpoint"] == "/tmp/ws"
    84→
    85→
    86→def test_delete_fuse_workspace(client):
    87→    resp = client.post("/workspaces/fuse",
    88→                       json={
    89→                           "mountpoint": "/tmp/ws",
    90→                           "pid": 99999
    91→                       })
    92→    ws_id = resp.json()["id"]
    93→    resp = client.delete(f"/workspaces/{ws_id}")
    94→    assert resp.status_code == 204
    95→    resp = client.get("/workspaces")
    96→    assert len(resp.json()) == 0
    97→
    98→
    99→def test_exec_on_fuse_returns_400(client):
   100→    resp = client.post("/workspaces/fuse",
   101→                       json={
   102→                           "mountpoint": "/tmp/ws",
   103→                           "pid": 12345
   104→                       })
   105→    ws_id = resp.json()["id"]
   106→    resp = client.post(f"/workspaces/{ws_id}/exec", json={"command": "ls /"})
   107→    assert resp.status_code == 400
   108→
   109→
   110→def test_create_workspace_per_mount_mode(client):
   111→    resp = client.post("/workspaces",
   112→                       json={
   113→                           "mounts": {
   114→                               "/ro/": "memory",
   115→                               "/rw/": {
   116→                                   "uri": "memory",
   117→                                   "mode": "write"
   118→                               },
   119→                           },
   120→                           "mode": "read",
   121→                       })
   122→    assert resp.status_code == 200
   123→    assert "id" in resp.json()
   124→
   125→
   126→def test_create_workspace_duplicate_prefix(client):
   127→    with pytest.raises(ValueError, match="duplicate mount prefix"):
   128→        client.post("/workspaces",
   129→                    json={"mounts": {
   130→                        "/data/": "memory",
   131→                        "/data": "memory",
   132→                    }})
   133→
   134→
   135→def test_create_workspace_with_runtime(client):
   136→    resp = client.post("/workspaces",
   137→                       json={
   138→                           "mounts": {
   139→                               "/tmp/": "memory"
   140→                           },
   141→                           "runtime": "memory",
   142→                       })
   143→    assert resp.status_code == 200
   144→    ws_id = resp.json()["id"]
   145→    data = client.get("/workspaces").json()
   146→    ws = [d for d in data if d["id"] == ws_id][0]
   147→    assert ws["runtime"] == "memory"
   148→
   149→
   150→def test_create_workspace_runtime_default_none(client):
   151→    resp = client.post("/workspaces", json={"mounts": {"/tmp/": "memory"}})
   152→    ws_id = resp.json()["id"]
   153→    data = client.get("/workspaces").json()
   154→    ws = [d for d in data if d["id"] == ws_id][0]
   155→    assert ws["runtime"] is None
   156→
   157→
   158→def test_runtime_blocks_dangerous_commands(client):
   159→    resp = client.post("/workspaces",
   160→                       json={
   161→                           "mounts": {
   162→                               "/tmp/": "memory"
   163→                           },
   164→                           "runtime": "memory",
   165→                       })
   166→    ws_id = resp.json()["id"]
   167→    resp = client.post(f"/workspaces/{ws_id}/exec",
   168→                       json={"command": "python -c 'print(1)'"})
   169→    assert resp.status_code == 200
   170→    data = resp.json()
   171→    assert data["ok"] is False
   172→    assert "not allowed" in data["error"]
   173→
   174→
   175→def test_runtime_allows_safe_commands(client):
   176→    resp = client.post("/workspaces",
   177→                       json={
   178→                           "mounts": {
   179→                               "/tmp/": "memory"
   180→                           },
   181→                           "runtime": "memory",
   182→                       })
   183→    ws_id = resp.json()["id"]
   184→    resp = client.post(f"/workspaces/{ws_id}/exec",
   185→                       json={"command": "ls /tmp/"})
   186→    assert resp.status_code == 200
   187→    assert resp.json()["ok"] is True
   188→
   189→
   190→def test_no_runtime_allows_all_commands(client):
   191→    resp = client.post("/workspaces", json={"mounts": {"/tmp/": "memory"}})
   192→    ws_id = resp.json()["id"]
   193→    resp = client.post(f"/workspaces/{ws_id}/exec",
   194→                       json={"command": "ls /tmp/"})
   195→    assert resp.status_code == 200
   196→    assert resp.json()["ok"] is True
   197→
   198→
   199→def test_register_fuse_with_runtime(client):
   200→    resp = client.post("/workspaces/fuse",
   201→                       json={
   202→                           "mountpoint": "/tmp/ws",
   203→                           "pid": 12345,
   204→                           "runtime": "local",
   205→                       })
   206→    assert resp.status_code == 200
   207→    ws_id = resp.json()["id"]
   208→    data = client.get("/workspaces").json()
   209→    ws = [d for d in data if d["id"] == ws_id][0]
   210→    assert ws["runtime"] == "local"
   211→
   212→
   213→def test_register_fuse_with_new_spec_format(client):
   214→    resp = client.post("/workspaces/fuse",
   215→                       json={
   216→                           "mountpoint": "/tmp/ws",
   217→                           "pid": 12345,
   218→                           "mount_specs": ["memory:/data/"],
   219→                       })
   220→    assert resp.status_code == 200
   221→    ws_id = resp.json()["id"]
   222→    data = client.get("/workspaces").json()
   223→    ws = [d for d in data if d["id"] == ws_id][0]
   224→    assert len(ws["mounts"]) == 1
   225→    assert ws["mounts"][0]["prefix"] == "/data/"
   226→    assert ws["mounts"][0]["uri"] == "memory"
   227→
   228→
   229→def test_create_workspace_with_consistency(client):
   230→    resp = client.post("/workspaces",
   231→                       json={
   232→                           "mounts": {
   233→                               "/data/": {
   234→                                   "uri": "memory",
   235→                                   "consistency": "lazy",
   236→                               },
   237→                           },
   238→                           "consistency": "ttl",
   239→                           "ttl_seconds": 60,
   240→                       })
   241→    assert resp.status_code == 200
   242→    assert "id" in resp.json()
   243→
   244→
   245→def test_list_fuse_ops(client):
   246→    resp = client.post("/workspaces", json={"mounts": {"/": "memory"}})
   247→    ws_id = resp.json()["id"]
   248→    resp = client.get(f"/workspaces/{ws_id}/fuse-ops")
   249→    assert resp.status_code == 200
   250→    ops = resp.json()
   251→    assert isinstance(ops, list)
   252→    assert any(o["pattern"] == "*" for o in ops)
   253→
   254→
   255→def test_register_fuse_op(client):
   256→    resp = client.post("/workspaces", json={"mounts": {"/": "memory"}})
   257→    ws_id = resp.json()["id"]
   258→    resp = client.post(f"/workspaces/{ws_id}/fuse-ops",
   259→                       json={
   260→                           "pattern": "*.bin",
   261→                           "handler": "s3"
   262→                       })
   263→    assert resp.status_code == 200
   264→    assert resp.json()["ok"] is True
   265→    ops = client.get(f"/workspaces/{ws_id}/fuse-ops").json()
   266→    assert any(o["pattern"] == "*.bin" and o["name"] == "s3" for o in ops)
   267→
   268→
   269→def test_register_fuse_op_unknown_handler(client):
   270→    resp = client.post("/workspaces", json={"mounts": {"/": "memory"}})
   271→    ws_id = resp.json()["id"]
   272→    resp = client.post(f"/workspaces/{ws_id}/fuse-ops",
   273→                       json={
   274→                           "pattern": "*",
   275→                           "handler": "nonexistent"
   276→                       })
   277→    assert resp.status_code == 400
   278→
   279→
   280→def test_exec_with_input_files(client):
   281→    resp = client.post("/workspaces",
   282→                       json={
   283→                           "mounts": {
   284→                               "/tmp/": "memory"
   285→                           },
   286→                           "mode": "write",
   287→                       })
   288→    ws_id = resp.json()["id"]
   289→
   290→    resp = client.post(
   291→        f"/workspaces/{ws_id}/exec",
   292→        json={
   293→            "command": "cat /tmp/in.txt",
   294→            "input_files": {
   295→                "/tmp/in.txt": {
   296→                    "checksum": "abc123",
   297→                    "data": base64.b64encode(b"hello from remote").decode(),
   298→                }
   299→            },
   300→        })
   301→    assert resp.status_code == 200
   302→    data = resp.json()
   303→    assert data["ok"] is True
   304→    assert "hello from remote" in data["output"]
   305→    assert "cache_status" in data
   306→    assert data["cache_status"]["/tmp/in.txt"] == "miss"
   307→
   308→
   309→def test_exec_with_cached_file(client):
   310→    resp = client.post("/workspaces",
   311→                       json={
   312→                           "mounts": {
   313→                               "/tmp/": "memory"
   314→                           },
   315→                           "mode": "write",
   316→                       })
   317→    ws_id = resp.json()["id"]
   318→
   319→    resp = client.post(f"/workspaces/{ws_id}/exec",
   320→                       json={
   321→                           "command": "cat /tmp/in.txt",
   322→                           "input_files": {
   323→                               "/tmp/in.txt": {
   324→                                   "checksum": "abc123",
   325→                                   "data": base64.b64encode(b"hello").decode(),
   326→                               }
   327→                           },
   328→                       })
   329→    assert resp.json()["cache_status"]["/tmp/in.txt"] == "miss"
   330→
   331→    resp = client.post(f"/workspaces/{ws_id}/exec",
   332→                       json={
   333→                           "command": "cat /tmp/in.txt",
   334→                           "input_files": {
   335→                               "/tmp/in.txt": {
   336→                                   "checksum": "abc123",
   337→                               }
   338→                           },
   339→                       })
   340→    assert resp.json()["ok"] is True
   341→    assert resp.json()["cache_status"]["/tmp/in.txt"] == "hit"
   342→
   343→
   344→def test_exec_with_stdin(client):
   345→    resp = client.post("/workspaces",
   346→                       json={
   347→                           "mounts": {
   348→                               "/tmp/": "memory"
   349→                           },
   350→                           "mode": "write",
   351→                       })
   352→    ws_id = resp.json()["id"]
   353→
   354→    resp = client.post(f"/workspaces/{ws_id}/exec",
   355→                       json={
   356→                           "command": "echo hello",
   357→                           "stdin":
   358→                           base64.b64encode(b"piped content").decode(),
   359→                       })
   360→    assert resp.status_code == 200
   361→    assert resp.json()["ok"] is True
   362→
   363→
   364→def test_delete_workspace_clears_cache(client):
   365→    resp = client.post("/workspaces",
   366→                       json={
   367→                           "mounts": {
   368→                               "/tmp/": "memory"
   369→                           },
   370→                           "mode": "write",
   371→                       })
   372→    ws_id = resp.json()["id"]
   373→
   374→    resp = client.post(f"/workspaces/{ws_id}/exec",
   375→                       json={
   376→                           "command": "echo hello",
   377→                           "input_files": {
   378→                               "/tmp/data.txt": {
   379→                                   "checksum": "abc",
   380→                                   "data": base64.b64encode(b"hello").decode(),
   381→                               }
   382→                           },
   383→                       })
   384→    assert resp.status_code == 200
   385→
   386→    resp = client.delete(f"/workspaces/{ws_id}")
   387→    assert resp.status_code == 204
   388→
   389→    resp = client.post(f"/workspaces/{ws_id}/exec",
   390→                       json={"command": "echo hi"})
   391→    assert resp.status_code == 404
   392→


     1→from mirage.server.service.cache import FileCache
     2→
     3→
     4→def test_put_and_get():
     5→    cache = FileCache()
     6→    cache.put("ws1", "/data/file.txt", "abc123", b"hello")
     7→    hit, data = cache.get("ws1", "/data/file.txt", "abc123")
     8→    assert hit is True
     9→    assert data == b"hello"
    10→
    11→
    12→def test_get_miss():
    13→    cache = FileCache()
    14→    hit, data = cache.get("ws1", "/data/file.txt", "abc123")
    15→    assert hit is False
    16→    assert data is None
    17→
    18→
    19→def test_get_checksum_mismatch():
    20→    cache = FileCache()
    21→    cache.put("ws1", "/data/file.txt", "abc123", b"hello")
    22→    hit, data = cache.get("ws1", "/data/file.txt", "def456")
    23→    assert hit is False
    24→    assert data is None
    25→
    26→
    27→def test_put_overwrites_on_mismatch():
    28→    cache = FileCache()
    29→    cache.put("ws1", "/data/file.txt", "abc123", b"hello")
    30→    cache.put("ws1", "/data/file.txt", "def456", b"world")
    31→    hit, data = cache.get("ws1", "/data/file.txt", "def456")
    32→    assert hit is True
    33→    assert data == b"world"
    34→
    35→
    36→def test_clear_workspace():
    37→    cache = FileCache()
    38→    cache.put("ws1", "/a.txt", "aaa", b"a")
    39→    cache.put("ws2", "/b.txt", "bbb", b"b")
    40→    cache.clear("ws1")
    41→    hit, _ = cache.get("ws1", "/a.txt", "aaa")
    42→    assert hit is False
    43→    hit, data = cache.get("ws2", "/b.txt", "bbb")
    44→    assert hit is True
    45→
    46→
    47→def test_isolation_between_workspaces():
    48→    cache = FileCache()
    49→    cache.put("ws1", "/same.txt", "abc", b"ws1-data")
    50→    cache.put("ws2", "/same.txt", "abc", b"ws2-data")
    51→    _, data1 = cache.get("ws1", "/same.txt", "abc")
    52→    _, data2 = cache.get("ws2", "/same.txt", "abc")
    53→    assert data1 == b"ws1-data"
    54→    assert data2 == b"ws2-data"
    55→


     1→import base64
     2→
     3→import pytest
     4→from fastapi.testclient import TestClient
     5→
     6→from mirage.observe import session_log as observe_session_module
     7→from mirage.server.fastapi import app as server_app_module
     8→from mirage.server.fastapi.app import app
     9→
    10→
    11→@pytest.fixture
    12→def client():
    13→    return TestClient(app)
    14→
    15→
    16→@pytest.fixture(autouse=True)
    17→def clear_sessions():
    18→    server_app_module._sessions.clear()
    19→    observe_session_module._session_logs.clear()
    20→    yield
    21→    server_app_module._sessions.clear()
    22→    observe_session_module._session_logs.clear()
    23→
    24→
    25→@pytest.fixture
    26→def ws_id(client):
    27→    resp = client.post("/workspaces", json={"mounts": {"/": "memory"}})
    28→    return resp.json()["id"]
    29→
    30→
    31→def test_write_read_roundtrip(client, ws_id):
    32→    client.post(f"/workspaces/{ws_id}/io/create", json={"path": "/f.txt"})
    33→    payload = base64.b64encode(b"hello world").decode()
    34→    resp = client.post(f"/workspaces/{ws_id}/io/write",
    35→                       json={
    36→                           "path": "/f.txt",
    37→                           "offset": 0,
    38→                           "data": payload
    39→                       })
    40→    assert resp.status_code == 200
    41→    assert resp.json()["written"] == 11
    42→
    43→    resp = client.post(f"/workspaces/{ws_id}/io/read",
    44→                       json={
    45→                           "path": "/f.txt",
    46→                           "offset": 0,
    47→                           "size": 11
    48→                       })
    49→    assert resp.status_code == 200
    50→    data = base64.b64decode(resp.json()["data"])
    51→    assert data == b"hello world"
    52→
    53→
    54→def test_stat(client, ws_id):
    55→    client.post(f"/workspaces/{ws_id}/io/create", json={"path": "/f.txt"})
    56→    payload = base64.b64encode(b"hello").decode()
    57→    client.post(f"/workspaces/{ws_id}/io/write",
    58→                json={
    59→                    "path": "/f.txt",
    60→                    "offset": 0,
    61→                    "data": payload
    62→                })
    63→    resp = client.post(f"/workspaces/{ws_id}/io/stat", json={"path": "/f.txt"})
    64→    assert resp.status_code == 200
    65→    assert resp.json()["size"] == 5
    66→
    67→
    68→def test_stat_missing_404(client, ws_id):
    69→    resp = client.post(f"/workspaces/{ws_id}/io/stat",
    70→                       json={"path": "/missing.txt"})
    71→    assert resp.status_code == 404
    72→
    73→
    74→def test_mkdir_readdir(client, ws_id):
    75→    client.post(f"/workspaces/{ws_id}/io/mkdir", json={"path": "/sub"})
    76→    resp = client.post(f"/workspaces/{ws_id}/io/readdir", json={"path": "/"})
    77→    assert resp.status_code == 200
    78→    entries = resp.json()["entries"]
    79→    names = [e.rsplit("/", 1)[-1] for e in entries]
    80→    assert "sub" in names
    81→
    82→
    83→def test_unlink(client, ws_id):
    84→    client.post(f"/workspaces/{ws_id}/io/create", json={"path": "/f.txt"})
    85→    resp = client.post(f"/workspaces/{ws_id}/io/unlink",
    86→                       json={"path": "/f.txt"})
    87→    assert resp.status_code == 200
    88→    resp = client.post(f"/workspaces/{ws_id}/io/stat", json={"path": "/f.txt"})
    89→    assert resp.status_code == 404
    90→
    91→
    92→def test_rename(client, ws_id):
    93→    client.post(f"/workspaces/{ws_id}/io/create", json={"path": "/a.txt"})
    94→    payload = base64.b64encode(b"data").decode()
    95→    client.post(f"/workspaces/{ws_id}/io/write",
    96→                json={
    97→                    "path": "/a.txt",
    98→                    "offset": 0,
    99→                    "data": payload
   100→                })
   101→    resp = client.post(f"/workspaces/{ws_id}/io/rename",
   102→                       json={
   103→                           "src": "/a.txt",
   104→                           "dst": "/b.txt"
   105→                       })
   106→    assert resp.status_code == 200
   107→    resp = client.post(f"/workspaces/{ws_id}/io/stat", json={"path": "/a.txt"})
   108→    assert resp.status_code == 404
   109→    resp = client.post(f"/workspaces/{ws_id}/io/stat", json={"path": "/b.txt"})
   110→    assert resp.status_code == 200
   111→
   112→
   113→def test_truncate(client, ws_id):
   114→    client.post(f"/workspaces/{ws_id}/io/create", json={"path": "/f.txt"})
   115→    payload = base64.b64encode(b"hello world").decode()
   116→    client.post(f"/workspaces/{ws_id}/io/write",
   117→                json={
   118→                    "path": "/f.txt",
   119→                    "offset": 0,
   120→                    "data": payload
   121→                })
   122→    resp = client.post(f"/workspaces/{ws_id}/io/truncate",
   123→                       json={
   124→                           "path": "/f.txt",
   125→                           "size": 5
   126→                       })
   127→    assert resp.status_code == 200
   128→    resp = client.post(f"/workspaces/{ws_id}/io/stat", json={"path": "/f.txt"})
   129→    assert resp.json()["size"] == 5
   130→
   131→
   132→def test_fuse_cat_default(client, ws_id):
   133→    client.post(f"/workspaces/{ws_id}/io/create", json={"path": "/f.txt"})
   134→    payload = base64.b64encode(b"hello").decode()
   135→    client.post(f"/workspaces/{ws_id}/io/write",
   136→                json={
   137→                    "path": "/f.txt",
   138→                    "offset": 0,
   139→                    "data": payload
   140→                })
   141→    resp = client.post(f"/workspaces/{ws_id}/io/fuse_cat",
   142→                       json={"path": "/f.txt"})
   143→    assert resp.status_code == 200
   144→    data = base64.b64decode(resp.json()["data"])
   145→    assert data == b"hello"
   146→
   147→
   148→def test_fuse_cat_missing_404(client, ws_id):
   149→    resp = client.post(f"/workspaces/{ws_id}/io/fuse_cat",
   150→                       json={"path": "/missing.txt"})
   151→    assert resp.status_code == 404
   152→
   153→
   154→def test_workspace_not_found(client):
   155→    resp = client.post("/workspaces/bad-id/io/stat", json={"path": "/f.txt"})
   156→    assert resp.status_code == 404
   157→


     1→import pytest
     2→
     3→from mirage.observe import session_log as observe_session_module
     4→from mirage.server.service.local import LocalService
     5→
     6→
     7→@pytest.fixture
     8→def svc():
     9→    s = LocalService()
    10→    observe_session_module._session_logs.clear()
    11→    yield s
    12→    s._sessions.clear()
    13→    observe_session_module._session_logs.clear()
    14→
    15→
    16→def test_create_and_list(svc):
    17→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
    18→    assert isinstance(ws_id, str)
    19→    workspaces = svc.list_workspaces()
    20→    assert len(workspaces) == 1
    21→    assert workspaces[0]["id"] == ws_id
    22→
    23→
    24→def test_exec_touch_ls(svc):
    25→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
    26→    ok, _, _, _ = svc.exec(ws_id, "touch /hello.txt")
    27→    assert ok
    28→    ok, output, _, _ = svc.exec(ws_id, "ls /")
    29→    assert ok
    30→    assert "hello.txt" in output
    31→
    32→
    33→def test_exec_blocked_with_runtime(svc):
    34→    ws_id = svc.create_workspace({
    35→        "mounts": {
    36→            "/": "memory"
    37→        },
    38→        "runtime": "memory",
    39→    })
    40→    ok, err, _, _ = svc.exec(ws_id, "python -c 'print(1)'")
    41→    assert not ok
    42→    assert "not allowed" in err
    43→
    44→
    45→def test_exec_not_blocked_without_runtime(svc):
    46→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
    47→    ok, _, _, _ = svc.exec(ws_id, "ls /")
    48→    assert ok
    49→
    50→
    51→def test_delete_workspace(svc):
    52→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
    53→    svc.delete_workspace(ws_id)
    54→    assert svc.list_workspaces() == []
    55→
    56→
    57→def test_delete_missing_raises(svc):
    58→    with pytest.raises(KeyError):
    59→        svc.delete_workspace("bad-id")
    60→
    61→
    62→def test_exec_missing_raises(svc):
    63→    with pytest.raises(KeyError):
    64→        svc.exec("bad-id", "ls /")
    65→
    66→
    67→def test_exec_on_fuse_raises(svc):
    68→    ws_id = svc.register_fuse(mountpoint="/tmp/ws", pid=99999)
    69→    with pytest.raises(ValueError, match="not supported"):
    70→        svc.exec(ws_id, "ls /")
    71→
    72→
    73→def test_sync(svc):
    74→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
    75→    results = svc.sync(ws_id)
    76→    assert isinstance(results, list)
    77→
    78→
    79→def test_status(svc):
    80→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
    81→    inodes = svc.status(ws_id)
    82→    assert isinstance(inodes, list)
    83→
    84→
    85→def test_list_commands(svc):
    86→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
    87→    cmds = svc.list_commands(ws_id)
    88→    assert "ls" in cmds
    89→    assert "cat" in cmds
    90→
    91→
    92→def test_register_command(svc):
    93→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
    94→    svc.register_command(ws_id, "hello", "echo hello")
    95→    cmds = svc.list_commands(ws_id)
    96→    assert "hello" in cmds
    97→
    98→
    99→def test_io_write_read(svc):
   100→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
   101→    svc.io_create(ws_id, "/f.txt")
   102→    written = svc.io_write(ws_id, "/f.txt", 0, b"hello world")
   103→    assert written == 11
   104→    data = svc.io_read(ws_id, "/f.txt", 0, 11)
   105→    assert data == b"hello world"
   106→
   107→
   108→def test_io_stat(svc):
   109→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
   110→    svc.io_create(ws_id, "/f.txt")
   111→    svc.io_write(ws_id, "/f.txt", 0, b"hello")
   112→    st = svc.io_stat(ws_id, "/f.txt")
   113→    assert st.size == 5
   114→
   115→
   116→def test_io_stat_missing(svc):
   117→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
   118→    with pytest.raises(FileNotFoundError):
   119→        svc.io_stat(ws_id, "/missing.txt")
   120→
   121→
   122→def test_io_readdir(svc):
   123→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
   124→    svc.io_create(ws_id, "/a.txt")
   125→    svc.io_create(ws_id, "/b.txt")
   126→    entries, cursor = svc.io_readdir(ws_id, "/")
   127→    names = [e.rsplit("/", 1)[-1] for e in entries]
   128→    assert "a.txt" in names
   129→    assert "b.txt" in names
   130→
   131→
   132→def test_io_mkdir_readdir(svc):
   133→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
   134→    svc.io_mkdir(ws_id, "/subdir")
   135→    entries, _ = svc.io_readdir(ws_id, "/")
   136→    names = [e.rsplit("/", 1)[-1] for e in entries]
   137→    assert "subdir" in names
   138→
   139→
   140→def test_io_unlink(svc):
   141→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
   142→    svc.io_create(ws_id, "/f.txt")
   143→    svc.io_unlink(ws_id, "/f.txt")
   144→    with pytest.raises(FileNotFoundError):
   145→        svc.io_stat(ws_id, "/f.txt")
   146→
   147→
   148→def test_io_rename(svc):
   149→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
   150→    svc.io_create(ws_id, "/a.txt")
   151→    svc.io_write(ws_id, "/a.txt", 0, b"data")
   152→    svc.io_rename(ws_id, "/a.txt", "/b.txt")
   153→    with pytest.raises(FileNotFoundError):
   154→        svc.io_stat(ws_id, "/a.txt")
   155→    st = svc.io_stat(ws_id, "/b.txt")
   156→    assert st.size == 4
   157→
   158→
   159→def test_io_truncate(svc):
   160→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
   161→    svc.io_create(ws_id, "/f.txt")
   162→    svc.io_write(ws_id, "/f.txt", 0, b"hello world")
   163→    svc.io_truncate(ws_id, "/f.txt", 5)
   164→    st = svc.io_stat(ws_id, "/f.txt")
   165→    assert st.size == 5
   166→
   167→
   168→def test_io_fuse_cat(svc):
   169→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
   170→    svc.io_create(ws_id, "/f.txt")
   171→    svc.io_write(ws_id, "/f.txt", 0, b"hello")
   172→    data = svc.io_fuse_cat(ws_id, "/f.txt")
   173→    assert data == b"hello"
   174→
   175→
   176→def test_list_fuse_ops(svc):
   177→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
   178→    ops = svc.list_fuse_ops(ws_id)
   179→    assert isinstance(ops, list)
   180→    assert any(o["pattern"] == "*" for o in ops)
   181→
   182→
   183→def test_register_fuse_op(svc):
   184→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
   185→    svc.register_fuse_op(ws_id, "*.bin", "s3")
   186→    ops = svc.list_fuse_ops(ws_id)
   187→    assert any(o["pattern"] == "*.bin" and o["name"] == "s3" for o in ops)
   188→
   189→
   190→def test_register_fuse_op_unknown(svc):
   191→    ws_id = svc.create_workspace({"mounts": {"/": "memory"}})
   192→    with pytest.raises(ValueError, match="unknown handler"):
   193→        svc.register_fuse_op(ws_id, "*", "nonexistent")
   194→


     1→from mirage.backend.local.memory import MemoryBackend
     2→from mirage.commands.custom.arg_resolver import classify_args
     3→from mirage.types import MountMode, SyncPolicy
     4→from mirage.workspace import Workspace
     5→
     6→
     7→def test_classify_input_file():
     8→    ws = Workspace({"/data/": MemoryBackend()},
     9→                   mode=MountMode.WRITE,
    10→                   sync_policy=SyncPolicy.IMMEDIATE)
    11→    ws.write("/data/input.json", b'{"key": "value"}')
    12→    result = classify_args(ws, ["input.json"], cwd="/data/")
    13→    assert "/data/input.json" in result.input_files
    14→    assert result.input_files["/data/input.json"] == b'{"key": "value"}'
    15→
    16→
    17→def test_classify_potential_output():
    18→    ws = Workspace({"/data/": MemoryBackend()}, mode=MountMode.WRITE)
    19→    result = classify_args(ws, ["/data/output.csv"], cwd="/")
    20→    assert "/data/output.csv" in result.potential_outputs
    21→
    22→
    23→def test_classify_directory():
    24→    ws = Workspace({"/data/": MemoryBackend()},
    25→                   mode=MountMode.WRITE,
    26→                   sync_policy=SyncPolicy.IMMEDIATE)
    27→    ws.mkdir("/data/dir/")
    28→    ws.write("/data/dir/a.txt", b"aaa")
    29→    ws.write("/data/dir/b.txt", b"bbb")
    30→    result = classify_args(ws, ["/data/dir/"], cwd="/")
    31→    assert "/data/dir/" in result.input_dirs
    32→    assert "/data/dir/a.txt" in result.input_dirs["/data/dir/"]
    33→    assert "/data/dir/b.txt" in result.input_dirs["/data/dir/"]
    34→
    35→
    36→def test_classify_regular_args():
    37→    ws = Workspace({"/data/": MemoryBackend()})
    38→    result = classify_args(ws, ["--verbose", "-o", "100"], cwd="/")
    39→    assert result.regular_args == ["--verbose", "-o", "100"]
    40→    assert result.input_files == {}
    41→    assert result.potential_outputs == []
    42→
    43→
    44→def test_classify_mixed_args():
    45→    ws = Workspace({"/data/": MemoryBackend()},
    46→                   mode=MountMode.WRITE,
    47→                   sync_policy=SyncPolicy.IMMEDIATE)
    48→    ws.write("/data/in.json", b"data")
    49→    result = classify_args(ws,
    50→                           ["/data/in.json", "-o", "/data/out.csv", "--flag"],
    51→                           cwd="/")
    52→    assert "/data/in.json" in result.input_files
    53→    assert "/data/out.csv" in result.potential_outputs
    54→    assert "--flag" in result.regular_args
    55→    assert "-o" in result.regular_args
    56→
    57→
    58→def test_classify_no_mount_match():
    59→    ws = Workspace({"/data/": MemoryBackend()})
    60→    result = classify_args(ws, ["/other/file.txt"], cwd="/")
    61→    assert result.input_files == {}
    62→    assert result.potential_outputs == []
    63→    assert "/other/file.txt" in result.regular_args
    64→
    65→
    66→def test_classify_relative_path():
    67→    ws = Workspace({"/data/": MemoryBackend()},
    68→                   mode=MountMode.WRITE,
    69→                   sync_policy=SyncPolicy.IMMEDIATE)
    70→    ws.mkdir("/data/sub/")
    71→    ws.write("/data/sub/file.txt", b"hello")
    72→    result = classify_args(ws, ["sub/file.txt"], cwd="/data/")
    73→    assert "/data/sub/file.txt" in result.input_files
    74→
    75→
    76→def test_classify_arg_map():
    77→    ws = Workspace({"/data/": MemoryBackend()},
    78→                   mode=MountMode.WRITE,
    79→                   sync_policy=SyncPolicy.IMMEDIATE)
    80→    ws.write("/data/file.txt", b"hello")
    81→    result = classify_args(ws, ["file.txt", "--flag"], cwd="/data/")
    82→    assert result.arg_map["file.txt"] == "/data/file.txt"
    83→    assert "file.txt" not in result.regular_args
    84→


     1→import sys
     2→from unittest.mock import MagicMock
     3→
     4→import pytest
     5→
     6→from mirage.backend.local.memory import MemoryBackend
     7→from mirage.commands.custom.registry import CommandRegistry, ExternalCommand
     8→from mirage.commands.remote.client import CommandClient
     9→from mirage.types import (CommandResult, MountMode, RemoteCommandOptions,
    10→                          SyncPolicy)
    11→from mirage.workspace import Workspace
    12→
    13→
    14→def test_builtin_ls():
    15→    ws = Workspace({"/tmp/": MemoryBackend()},
    16→                   mode=MountMode.WRITE,
    17→                   sync_policy=SyncPolicy.IMMEDIATE)
    18→    ws.write("/tmp/a.txt", b"hi")
    19→    reg = CommandRegistry()
    20→    result = reg.run(ws, "ls /tmp/", cwd="/")
    21→    assert "a.txt" in result
    22→
    23→
    24→def test_register_external():
    25→    reg = CommandRegistry()
    26→    reg.register(ExternalCommand(name="echo-test", exec="echo hello"))
    27→    assert "echo-test" in reg.list_commands()
    28→
    29→
    30→def test_run_external_no_placeholder(tmp_path):
    31→    reg = CommandRegistry()
    32→    reg.register(ExternalCommand(name="greet", exec="echo hello"))
    33→    ws = Workspace({"/tmp/": MemoryBackend()})
    34→    result = reg.run(ws, "greet", cwd="/")
    35→    assert "hello" in result
    36→
    37→
    38→def test_run_external_file_placeholder(tmp_path):
    39→    ws = Workspace({"/tmp/": MemoryBackend()}, mode=MountMode.WRITE)
    40→    ws.write("/tmp/data.txt", b"line1\nline2")
    41→    script = tmp_path / "count.py"
    42→    script.write_text(
    43→        "import sys; lines = open(sys.argv[1]).readlines(); print(len(lines))")
    44→    reg = CommandRegistry()
    45→    reg.register(
    46→        ExternalCommand(name="linecount", exec=f"{sys.executable} {script}"))
    47→    result = reg.run(ws, "linecount /tmp/data.txt", cwd="/")
    48→    assert result.strip() == "2"
    49→
    50→
    51→def test_unknown_command():
    52→    reg = CommandRegistry()
    53→    ws = Workspace({"/tmp/": MemoryBackend()})
    54→    with pytest.raises(ValueError, match="unknown command"):
    55→        reg.run(ws, "nonexistent", cwd="/")
    56→
    57→
    58→def test_run_external_file_placeholder_multi_arg(tmp_path):
    59→    ws = Workspace({"/tmp/": MemoryBackend()}, mode=MountMode.WRITE)
    60→    ws.write("/tmp/a.txt", b"aaa")
    61→    ws.write("/tmp/b.txt", b"bbb")
    62→    script = tmp_path / "concat.py"
    63→    script.write_text(
    64→        "import sys; [print(open(f).read(), end='') for f in sys.argv[1:]]")
    65→    reg = CommandRegistry()
    66→    reg.register(
    67→        ExternalCommand(name="concat", exec=f"{sys.executable} {script}"))
    68→    result = reg.run(ws, "concat /tmp/a.txt /tmp/b.txt", cwd="/")
    69→    assert "aaa" in result
    70→    assert "bbb" in result
    71→
    72→
    73→def test_external_overrides_builtin():
    74→    ws = Workspace({"/tmp/": MemoryBackend()},
    75→                   mode=MountMode.WRITE,
    76→                   sync_policy=SyncPolicy.IMMEDIATE)
    77→    ws.write("/tmp/file.txt", b"data")
    78→    reg = CommandRegistry()
    79→    reg.register(ExternalCommand(name="ls", exec="echo overridden"))
    80→    result = reg.run(ws, "ls /tmp/", cwd="/")
    81→    assert "overridden" in result
    82→    assert "file.txt" not in result
    83→
    84→
    85→class _CustomMethodBackend(MemoryBackend):
    86→
    87→    def lllll(self, path: str) -> str:
    88→        return f"custom-result-for:{path}"
    89→
    90→    def bytes_method(self, path: str) -> bytes:
    91→        return b"raw-bytes"
    92→
    93→    def list_method(self, path: str) -> list:
    94→        return ["item1", "item2", "item3"]
    95→
    96→
    97→def test_backend_method_fallback():
    98→    backend = _CustomMethodBackend()
    99→    ws = Workspace({"/data/": backend}, mode=MountMode.WRITE)
   100→    reg = CommandRegistry()
   101→    result = reg.run(ws, "lllll /data/", cwd="/")
   102→    assert "custom-result-for:" in result
   103→
   104→
   105→def test_backend_method_fallback_bytes_coercion():
   106→    backend = _CustomMethodBackend()
   107→    ws = Workspace({"/data/": backend}, mode=MountMode.WRITE)
   108→    reg = CommandRegistry()
   109→    result = reg.run(ws, "bytes_method /data/", cwd="/")
   110→    assert result == "raw-bytes"
   111→
   112→
   113→def test_backend_method_fallback_list_coercion():
   114→    backend = _CustomMethodBackend()
   115→    ws = Workspace({"/data/": backend}, mode=MountMode.WRITE)
   116→    reg = CommandRegistry()
   117→    result = reg.run(ws, "list_method /data/", cwd="/")
   118→    assert "item1\nitem2\nitem3" == result
   119→
   120→
   121→def test_backend_private_method_rejected():
   122→    backend = _CustomMethodBackend()
   123→    ws = Workspace({"/data/": backend}, mode=MountMode.WRITE)
   124→    reg = CommandRegistry()
   125→    with pytest.raises(ValueError, match="unknown command"):
   126→        reg.run(ws, "_files /data/", cwd="/")
   127→
   128→
   129→def test_list_commands_includes_builtins():
   130→    registry = CommandRegistry()
   131→    cmds = registry.list_commands()
   132→    assert "ls" in cmds
   133→    assert "cat" in cmds
   134→
   135→
   136→def test_run_builtin_cat():
   137→    backend = MemoryBackend()
   138→    backend.put("/hello.txt", b"hello world")
   139→    ws = Workspace({"/data/": backend}, mode=MountMode.READ)
   140→    result = CommandRegistry().run(ws, "cat /data/hello.txt", cwd="/")
   141→    assert "hello world" in result
   142→
   143→
   144→def test_external_command_with_options():
   145→    cmd = ExternalCommand(
   146→        name="transform",
   147→        exec="transform",
   148→        options=RemoteCommandOptions(max_file_size="500MB", cache="disabled"),
   149→    )
   150→    assert cmd.options.max_file_size == "500MB"
   151→    assert cmd.options.cache == "disabled"
   152→
   153→
   154→def test_external_command_default_options():
   155→    cmd = ExternalCommand(name="echo-test", exec="echo hello")
   156→    assert cmd.options.max_file_size == "100MB"
   157→
   158→
   159→def test_external_command_default_options_not_none():
   160→    cmd = ExternalCommand(name="echo-test", exec="echo hello")
   161→    assert cmd.options is not None
   162→    assert cmd.options.max_file_size == "100MB"
   163→
   164→
   165→def test_run_external_out_placeholder(tmp_path):
   166→    ws = Workspace({"/tmp/": MemoryBackend()},
   167→                   mode=MountMode.WRITE,
   168→                   sync_policy=SyncPolicy.IMMEDIATE)
   169→    ws.write("/tmp/input.txt", b"hello world")
   170→    script = tmp_path / "upper.py"
   171→    script.write_text("import sys\n"
   172→                      "data = open(sys.argv[1]).read()\n"
   173→                      "open(sys.argv[2], 'w').write(data.upper())\n"
   174→                      "print('done')\n")
   175→    reg = CommandRegistry()
   176→    reg.register(
   177→        ExternalCommand(
   178→            name="upper",
   179→            exec=f"{sys.executable} {script}",
   180→        ))
   181→    result = reg.run(ws, "upper /tmp/input.txt /tmp/output.txt", cwd="/")
   182→    assert "done" in result
   183→    output = ws.cat("/tmp/output.txt")
   184→    assert output == b"HELLO WORLD"
   185→
   186→
   187→def test_registry_dispatches_to_client_with_files():
   188→    mock_client = MagicMock(spec=CommandClient)
   189→    mock_client.run.return_value = CommandResult(
   190→        stdout="done",
   191→        output_files={"/tmp/out.csv": b"result"},
   192→    )
   193→
   194→    ws = Workspace({"/tmp/": MemoryBackend()},
   195→                   mode=MountMode.WRITE,
   196→                   sync_policy=SyncPolicy.IMMEDIATE)
   197→    ws.write("/tmp/input.json", b'{"data": true}')
   198→
   199→    reg = CommandRegistry(client=mock_client)
   200→    reg.register(ExternalCommand(
   201→        name="transform",
   202→        exec="transform",
   203→    ))
   204→    result = reg.run(ws, "transform /tmp/input.json /tmp/out.csv", cwd="/")
   205→    assert "done" in result
   206→
   207→    call_kwargs = mock_client.run.call_args
   208→    assert call_kwargs.kwargs.get("files") is not None
   209→    files = call_kwargs.kwargs["files"]
   210→    assert "/tmp/input.json" in files
   211→    assert files["/tmp/input.json"].data == b'{"data": true}'
   212→    assert files["/tmp/input.json"].checksum is not None
   213→
   214→    output = ws.cat("/tmp/out.csv")
   215→    assert output == b"result"
   216→
   217→
   218→def test_registry_dispatches_to_client_with_stdin():
   219→    mock_client = MagicMock(spec=CommandClient)
   220→    mock_client.run.return_value = CommandResult(stdout="processed")
   221→
   222→    ws = Workspace({"/tmp/": MemoryBackend()})
   223→    reg = CommandRegistry(client=mock_client)
   224→    reg.register(ExternalCommand(name="process", exec="process"))
   225→    result = reg.run(ws, "process", cwd="/", stdin=b"pipe input")
   226→    assert "processed" in result
   227→    call_kwargs = mock_client.run.call_args
   228→    assert call_kwargs.kwargs.get("stdin") == b"pipe input"
   229→
   230→
   231→def test_registry_client_no_placeholders_still_uses_client():
   232→    mock_client = MagicMock(spec=CommandClient)
   233→    mock_client.run.return_value = CommandResult(stdout="remote echo")
   234→
   235→    ws = Workspace({"/tmp/": MemoryBackend()})
   236→    reg = CommandRegistry(client=mock_client)
   237→    reg.register(ExternalCommand(name="remote-echo", exec="echo hello"))
   238→    result = reg.run(ws, "remote-echo", cwd="/")
   239→    assert "remote echo" in result
   240→    mock_client.run.assert_called_once()
   241→
   242→
   243→def test_registry_out_to_readonly_mount_fails():
   244→    mock_client = MagicMock(spec=CommandClient)
   245→    ws = Workspace({"/data/": MemoryBackend()}, mode=MountMode.READ)
   246→    reg = CommandRegistry(client=mock_client)
   247→    reg.register(ExternalCommand(name="transform", exec="transform"))
   248→    with pytest.raises(PermissionError, match="read-only"):
   249→        reg.run(ws, "transform /data/in.json /data/out.csv", cwd="/")
   250→
   251→
   252→def test_registry_rejects_oversized_file():
   253→    mock_client = MagicMock(spec=CommandClient)
   254→    ws = Workspace({"/tmp/": MemoryBackend()},
   255→                   mode=MountMode.WRITE,
   256→                   sync_policy=SyncPolicy.IMMEDIATE)
   257→    ws.write("/tmp/big.bin", b"x" * 200)
   258→
   259→    reg = CommandRegistry(client=mock_client)
   260→    reg.register(
   261→        ExternalCommand(
   262→            name="process",
   263→            exec="process",
   264→            options=RemoteCommandOptions(max_file_size="100B"),
   265→        ))
   266→    with pytest.raises(ValueError, match="exceeds max"):
   267→        reg.run(ws, "process /tmp/big.bin", cwd="/")
   268→
   269→
   270→def test_registry_allows_file_within_size_limit():
   271→    mock_client = MagicMock(spec=CommandClient)
   272→    mock_client.run.return_value = CommandResult(stdout="ok")
   273→
   274→    ws = Workspace({"/tmp/": MemoryBackend()},
   275→                   mode=MountMode.WRITE,
   276→                   sync_policy=SyncPolicy.IMMEDIATE)
   277→    ws.write("/tmp/small.txt", b"small")
   278→
   279→    reg = CommandRegistry(client=mock_client)
   280→    reg.register(
   281→        ExternalCommand(
   282→            name="process",
   283→            exec="process",
   284→            options=RemoteCommandOptions(max_file_size="100B"),
   285→        ))
   286→    result = reg.run(ws, "process /tmp/small.txt", cwd="/")
   287→    assert "ok" in result
   288→
   289→
   290→def test_parse_size_units():
   291→    from mirage.server.constants import _parse_size
   292→    assert _parse_size("100B") == 100
   293→    assert _parse_size("10KB") == 10240
   294→    assert _parse_size("1MB") == 1048576
   295→    assert _parse_size("2GB") == 2147483648
   296→    assert _parse_size("100") == 100
   297→
   298→
   299→def test_run_external_no_template_input_output(tmp_path):
   300→    ws = Workspace({"/tmp/": MemoryBackend()},
   301→                   mode=MountMode.WRITE,
   302→                   sync_policy=SyncPolicy.IMMEDIATE)
   303→    ws.write("/tmp/input.txt", b"hello world")
   304→    script = tmp_path / "upper.py"
   305→    script.write_text("import sys\n"
   306→                      "data = open(sys.argv[1]).read()\n"
   307→                      "open(sys.argv[2], 'w').write(data.upper())\n"
   308→                      "print('done')\n")
   309→    reg = CommandRegistry()
   310→    reg.register(
   311→        ExternalCommand(name="upper", exec=f"{sys.executable} {script}"))
   312→    result = reg.run(ws, "upper /tmp/input.txt /tmp/output.txt", cwd="/")
   313→    assert "done" in result
   314→    output = ws.cat("/tmp/output.txt")
   315→    assert output == b"HELLO WORLD"
   316→
   317→
   318→def test_run_external_no_template_inplace(tmp_path):
   319→    ws = Workspace({"/tmp/": MemoryBackend()},
   320→                   mode=MountMode.WRITE,
   321→                   sync_policy=SyncPolicy.IMMEDIATE)
   322→    ws.write("/tmp/data.txt", b"hello")
   323→    script = tmp_path / "inplace.py"
   324→    script.write_text("import sys\n"
   325→                      "path = sys.argv[1]\n"
   326→                      "data = open(path).read()\n"
   327→                      "open(path, 'w').write(data.upper())\n"
   328→                      "print('done')\n")
   329→    reg = CommandRegistry()
   330→    reg.register(
   331→        ExternalCommand(name="format", exec=f"{sys.executable} {script}"))
   332→    result = reg.run(ws, "format /tmp/data.txt", cwd="/")
   333→    assert "done" in result
   334→    assert ws.cat("/tmp/data.txt") == b"HELLO"
   335→
   336→
   337→def test_run_external_no_template_no_files():
   338→    ws = Workspace({"/tmp/": MemoryBackend()})
   339→    reg = CommandRegistry()
   340→    reg.register(ExternalCommand(name="greet", exec="echo"))
   341→    result = reg.run(ws, "greet hello world", cwd="/")
   342→    assert "hello world" in result
   343→
   344→
   345→def test_dispatch_auto_detect_input_and_output():
   346→    mock_client = MagicMock(spec=CommandClient)
   347→    mock_client.run.return_value = CommandResult(
   348→        stdout="done",
   349→        output_files={"/tmp/out.csv": b"result"},
   350→    )
   351→    ws = Workspace({"/tmp/": MemoryBackend()},
   352→                   mode=MountMode.WRITE,
   353→                   sync_policy=SyncPolicy.IMMEDIATE)
   354→    ws.write("/tmp/input.json", b'{"data": true}')
   355→
   356→    reg = CommandRegistry(client=mock_client)
   357→    reg.register(ExternalCommand(name="transform", exec="transform"))
   358→    result = reg.run(ws, "transform /tmp/input.json /tmp/out.csv", cwd="/")
   359→    assert "done" in result
   360→
   361→    call_kwargs = mock_client.run.call_args
   362→    files = call_kwargs.kwargs.get("files")
   363→    assert files is not None
   364→    assert "/tmp/input.json" in files
   365→    assert files["/tmp/input.json"].data == b'{"data": true}'
   366→
   367→    output = ws.cat("/tmp/out.csv")
   368→    assert output == b"result"
   369→
   370→
   371→def test_dispatch_auto_detect_inplace():
   372→    mock_client = MagicMock(spec=CommandClient)
   373→    mock_client.run.return_value = CommandResult(
   374→        stdout="formatted",
   375→        output_files={"/tmp/data.json": b'{"formatted": true}'},
   376→    )
   377→    ws = Workspace({"/tmp/": MemoryBackend()},
   378→                   mode=MountMode.WRITE,
   379→                   sync_policy=SyncPolicy.IMMEDIATE)
   380→    ws.write("/tmp/data.json", b'{"data": true}')
   381→
   382→    reg = CommandRegistry(client=mock_client)
   383→    reg.register(ExternalCommand(name="format", exec="format"))
   384→    result = reg.run(ws, "format /tmp/data.json", cwd="/")
   385→    assert "formatted" in result
   386→    assert ws.cat("/tmp/data.json") == b'{"formatted": true}'
   387→
   388→
   389→def test_dispatch_auto_detect_no_files():
   390→    mock_client = MagicMock(spec=CommandClient)
   391→    mock_client.run.return_value = CommandResult(stdout="hello")
   392→    ws = Workspace({"/tmp/": MemoryBackend()})
   393→    reg = CommandRegistry(client=mock_client)
   394→    reg.register(ExternalCommand(name="echo", exec="echo"))
   395→    result = reg.run(ws, "echo hello", cwd="/")
   396→    assert "hello" in result
   397→
   398→
   399→def test_dispatch_locks_paths_during_execution():
   400→    mock_client = MagicMock(spec=CommandClient)
   401→    locked_during_run = []
   402→
   403→    def capture_locks(*args, **kwargs):
   404→        locked_during_run.extend(list(ws._locked_paths))
   405→        return CommandResult(stdout="ok")
   406→
   407→    mock_client.run.side_effect = capture_locks
   408→    ws = Workspace({"/tmp/": MemoryBackend()},
   409→                   mode=MountMode.WRITE,
   410→                   sync_policy=SyncPolicy.IMMEDIATE)
   411→    ws.write("/tmp/file.txt", b"data")
   412→    reg = CommandRegistry(client=mock_client)
   413→    reg.register(ExternalCommand(name="process", exec="process"))
   414→    reg.run(ws, "process /tmp/file.txt", cwd="/")
   415→    assert "/tmp/file.txt" in locked_during_run
   416→    assert len(ws._locked_paths) == 0
   417→
   418→
   419→def test_dispatch_size_limit_enforced():
   420→    mock_client = MagicMock(spec=CommandClient)
   421→    ws = Workspace({"/tmp/": MemoryBackend()},
   422→                   mode=MountMode.WRITE,
   423→                   sync_policy=SyncPolicy.IMMEDIATE)
   424→    ws.write("/tmp/big.bin", b"x" * 200)
   425→    reg = CommandRegistry(client=mock_client)
   426→    reg.register(
   427→        ExternalCommand(
   428→            name="process",
   429→            exec="process",
   430→            options=RemoteCommandOptions(max_file_size="100B"),
   431→        ))
   432→    with pytest.raises(ValueError, match="exceeds max"):
   433→        reg.run(ws, "process /tmp/big.bin", cwd="/")
   434→
   435→
   436→def test_dispatch_output_to_readonly_fails():
   437→    mock_client = MagicMock(spec=CommandClient)
   438→    ws = Workspace({"/data/": MemoryBackend()}, mode=MountMode.READ)
   439→    reg = CommandRegistry(client=mock_client)
   440→    reg.register(ExternalCommand(name="transform", exec="transform"))
   441→    with pytest.raises(PermissionError, match="read-only"):
   442→        reg.run(ws, "transform /data/in.json /data/out.csv", cwd="/")
   443→


     1→import pytest
     2→from fastapi.testclient import TestClient
     3→
     4→from mirage.observe import session_log as observe_session_module
     5→from mirage.server.fastapi import app as server_app_module
     6→from mirage.server.fastapi.app import app
     7→
     8→
     9→@pytest.fixture
    10→def client():
    11→    return TestClient(app)
    12→
    13→
    14→@pytest.fixture(autouse=True)
    15→def clear_sessions():
    16→    server_app_module._sessions.clear()
    17→    observe_session_module._session_logs.clear()
    18→    yield
    19→    server_app_module._sessions.clear()
    20→    observe_session_module._session_logs.clear()
    21→


     1→import pytest
     2→
     3→from mirage.backend.local.memory import MemoryBackend
     4→from mirage.types import MountMode, SyncPolicy
     5→from mirage.workspace import Workspace
     6→
     7→
     8→@pytest.fixture
     9→def memory_backend():
    10→    return MemoryBackend()
    11→
    12→
    13→@pytest.fixture
    14→def write_ws():
    15→    return Workspace(
    16→        {"/tmp/": MemoryBackend()},
    17→        mode=MountMode.WRITE,
    18→        sync_policy=SyncPolicy.IMMEDIATE,
    19→    )
    20→
    21→
    22→@pytest.fixture
    23→def staged_ws():
    24→    return Workspace(
    25→        {"/tmp/": MemoryBackend()},
    26→        mode=MountMode.WRITE,
    27→        sync_policy=SyncPolicy.STAGED,
    28→    )
    29→


     1→import pytest
     2→
     3→from mirage.commands.custom.registry import CommandRegistry
     4→from mirage.runtime.local import LocalRuntime
     5→from mirage.types import MountMode
     6→
     7→
     8→def test_local_runtime_ls(tmp_path):
     9→    rt = LocalRuntime(str(tmp_path))
    10→    result = rt.run("ls /")
    11→    assert isinstance(result, str)
    12→
    13→
    14→def test_local_runtime_cat(tmp_path):
    15→    (tmp_path / "hello.txt").write_bytes(b"world")
    16→    rt = LocalRuntime(str(tmp_path))
    17→    result = rt.run("cat /hello.txt")
    18→    assert "world" in result
    19→
    20→
    21→def test_local_runtime_write_file(tmp_path):
    22→    rt = LocalRuntime(str(tmp_path))
    23→    rt.run("touch /new.txt")
    24→    assert (tmp_path / "new.txt").exists()
    25→
    26→
    27→def test_local_runtime_read_mode_raises_on_write(tmp_path):
    28→    rt = LocalRuntime(str(tmp_path), mode=MountMode.READ)
    29→    with pytest.raises(Exception):
    30→        rt.ws.write("/blocked.txt", b"data")
    31→
    32→
    33→def test_local_runtime_custom_registry(tmp_path):
    34→    registry = CommandRegistry()
    35→    rt = LocalRuntime(str(tmp_path), registry=registry)
    36→    assert rt._registry is registry
    37→


     1→import pytest
     2→from fastapi.testclient import TestClient
     3→
     4→from mirage.observe import session_log as observe_session_module
     5→from mirage.runtime.base import CommandNotAllowedError
     6→from mirage.runtime.remote import RemoteRuntime
     7→from mirage.server.fastapi import app as server_app_module
     8→from mirage.server.fastapi.app import app
     9→
    10→
    11→@pytest.fixture
    12→def rt():
    13→    server_app_module._sessions.clear()
    14→    observe_session_module._session_logs.clear()
    15→    client = TestClient(app)
    16→    resp = client.post("/workspaces",
    17→                       json={
    18→                           "mounts": {
    19→                               "/": "memory"
    20→                           },
    21→                           "runtime": "memory"
    22→                       })
    23→    ws_id = resp.json()["id"]
    24→    runtime = RemoteRuntime("http://testserver", ws_id, http_client=client)
    25→    yield runtime
    26→    server_app_module._sessions.clear()
    27→    observe_session_module._session_logs.clear()
    28→
    29→
    30→def test_exec_touch_ls(rt):
    31→    rt.run("touch /hello.txt")
    32→    output = rt.run("ls /")
    33→    assert "hello.txt" in output
    34→
    35→
    36→def test_blocked_command(rt):
    37→    with pytest.raises(CommandNotAllowedError):
    38→        rt.run("python -c 'print(1)'")
    39→
    40→
    41→def test_cd_updates_cwd(rt):
    42→    rt.run("mkdir /sub")
    43→    rt.run("cd /sub")
    44→    assert rt._cwd == "/sub"
    45→
    46→
    47→def test_whoami(rt):
    48→    output = rt.run("whoami")
    49→    assert "agent:" in output
    50→    assert "cwd: /" in output
    51→
    52→
    53→def test_identify(rt):
    54→    output = rt.run("identify test-agent")
    55→    assert output == "test-agent"
    56→    assert rt.agent_id == "test-agent"
    57→
    58→
    59→def test_identify_read(rt):
    60→    rt.agent_id = "my-agent"
    61→    output = rt.run("identify")
    62→    assert output == "my-agent"
    63→
    64→
    65→def test_empty_command(rt):
    66→    output = rt.run("")
    67→    assert output == ""
    68→
    69→
    70→def test_touch_stat(rt):
    71→    rt.run("touch /f.txt")
    72→    output = rt.run("stat /f.txt")
    73→    assert "f.txt" in output
    74→
    75→
    76→def test_cd_no_args(rt):
    77→    rt.run("mkdir /sub")
    78→    rt.run("cd /sub")
    79→    rt.run("cd")
    80→    assert rt._cwd == "/"
    81→
    82→
    83→def test_remote_commands_list(rt):
    84→    cmds = rt.commands.list()
    85→    assert "ls" in cmds
    86→    assert "cat" in cmds
    87→
    88→
    89→def test_remote_commands_register(rt):
    90→    rt.commands.register("hello", "echo hello")
    91→    assert "hello" in rt.commands.list()
    92→
    93→
    94→def test_remote_fuse_ops_list(rt):
    95→    ops = rt.fuse_ops.list()
    96→    assert isinstance(ops, list)
    97→    assert any(o["pattern"] == "*" for o in ops)
    98→
    99→
   100→def test_remote_fuse_ops_register(rt):
   101→    rt.fuse_ops.register("*.parquet", "parquet")
   102→    ops = rt.fuse_ops.list()
   103→    assert any(o["pattern"] == "*.parquet" for o in ops)
   104→
   105→
   106→def test_remote_workspace_sync(rt):
   107→    result = rt.workspace.sync()
   108→    assert isinstance(result, list)
   109→
   110→
   111→def test_remote_workspace_status(rt):
   112→    result = rt.workspace.status()
   113→    assert isinstance(result, list)
   114→
   115→
   116→def test_remote_cwd_sent_with_exec(rt):
   117→    rt.run("mkdir /sub")
   118→    rt.run("cd /sub")
   119→    rt.run("touch foo.txt")
   120→    output = rt.run("ls /sub")
   121→    assert "foo.txt" in output
   122→


     1→import pytest
     2→
     3→from mirage.backend.local.memory import MemoryBackend
     4→from mirage.runtime.base import CommandNotAllowedError, Runtime
     5→from mirage.types import MountMode, SyncPolicy
     6→from mirage.workspace import Workspace
     7→
     8→
     9→@pytest.fixture
    10→def runtime():
    11→    ws = Workspace(
    12→        {"/tmp/": MemoryBackend()},
    13→        mode=MountMode.WRITE,
    14→        sync_policy=SyncPolicy.IMMEDIATE,
    15→    )
    16→    return Runtime(ws)
    17→
    18→
    19→def test_empty_command_returns_empty_string(runtime):
    20→    assert runtime.run("") == ""
    21→
    22→
    23→def test_blocked_python_raises(runtime):
    24→    with pytest.raises(CommandNotAllowedError):
    25→        runtime.run("python script.py")
    26→
    27→
    28→def test_blocked_bash_raises(runtime):
    29→    with pytest.raises(CommandNotAllowedError):
    30→        runtime.run("bash -c 'echo hi'")
    31→
    32→
    33→def test_blocked_sudo_raises(runtime):
    34→    with pytest.raises(CommandNotAllowedError):
    35→        runtime.run("sudo ls")
    36→
    37→
    38→def test_unknown_command_raises(runtime):
    39→    with pytest.raises(ValueError, match="unknown command"):
    40→        runtime.run("nonexistent_cmd")
    41→
    42→
    43→def test_cd_changes_cwd(runtime):
    44→    runtime.ws.mkdir("/tmp/sub/")
    45→    runtime.run("cd /tmp/sub/")
    46→    assert runtime._cwd == "/tmp/sub/"
    47→
    48→
    49→def test_cd_no_args_goes_to_root(runtime):
    50→    runtime.ws.mkdir("/tmp/sub/")
    51→    runtime.run("cd /tmp/sub/")
    52→    runtime.run("cd")
    53→    assert runtime._cwd == "/"
    54→
    55→
    56→def test_cd_resets_cwd(runtime):
    57→    runtime.ws.mkdir("/tmp/sub/")
    58→    runtime.run("cd /tmp/sub/")
    59→    runtime.run("cd /tmp/")
    60→    assert runtime._cwd == "/tmp/"
    61→
    62→
    63→def test_whoami_always_has_agent_id(runtime):
    64→    result = runtime.run("whoami")
    65→    assert "agent:" in result
    66→    assert "cwd:" in result
    67→
    68→
    69→def test_whoami_with_agent_id():
    70→    ws = Workspace(
    71→        {"/tmp/": MemoryBackend()},
    72→        mode=MountMode.WRITE,
    73→        sync_policy=SyncPolicy.IMMEDIATE,
    74→    )
    75→    rt = Runtime(ws, agent_id="claude-code")
    76→    result = rt.run("whoami")
    77→    assert "agent: claude-code" in result
    78→    assert "cwd: /" in result
    79→
    80→
    81→def test_identify_sets_agent_id(runtime):
    82→    runtime.run("identify my-agent")
    83→    assert runtime.agent_id == "my-agent"
    84→
    85→
    86→def test_identify_returns_agent_id(runtime):
    87→    result = runtime.run("identify claude-session-42")
    88→    assert result == "claude-session-42"
    89→
    90→
    91→def test_whoami_shows_agent_after_identify(runtime):
    92→    runtime.run("identify test-agent")
    93→    result = runtime.run("whoami")
    94→    assert "agent: test-agent" in result
    95→
    96→
    97→def test_identify_with_spaces(runtime):
    98→    runtime.run("identify Claude Code Agent")
    99→    assert runtime.agent_id == "Claude Code Agent"
   100→


     1→import pytest
     2→
     3→from mirage.backend.local.memory import MemoryBackend
     4→from mirage.runtime.memory import MemoryRuntime
     5→from mirage.types import MountMode, SyncPolicy
     6→from mirage.workspace import Workspace
     7→
     8→
     9→@pytest.fixture
    10→def runtime():
    11→    ws = Workspace(
    12→        {"/tmp/": MemoryBackend()},
    13→        mode=MountMode.WRITE,
    14→        sync_policy=SyncPolicy.IMMEDIATE,
    15→    )
    16→    return MemoryRuntime(ws)
    17→
    18→
    19→def test_run_ls_empty(runtime):
    20→    result = runtime.run("ls /tmp/")
    21→    assert isinstance(result, str)
    22→
    23→
    24→def test_run_ls_with_files(runtime):
    25→    runtime.ws.write("/tmp/a.txt", b"hello")
    26→    result = runtime.run("ls /tmp/")
    27→    assert "a.txt" in result
    28→
    29→
    30→def test_run_cat(runtime):
    31→    runtime.ws.write("/tmp/f.txt", b"content")
    32→    result = runtime.run("cat /tmp/f.txt")
    33→    assert "content" in result
    34→
    35→
    36→def test_run_pwd(runtime):
    37→    assert runtime.run("pwd") == "/"
    38→


     1→import pytest
     2→
     3→from mirage.backend.local.memory import MemoryBackend
     4→from mirage.commands.custom.registry import CommandRegistry
     5→from mirage.fuse.registry import FuseOpRegistry
     6→from mirage.runtime import MemoryRuntime
     7→from mirage.runtime.accessors.local import (LocalCommandsAccessor,
     8→                                            LocalFuseOpsAccessor,
     9→                                            LocalWorkspaceOpsAccessor)
    10→from mirage.types import MountMode
    11→from mirage.workspace import Workspace
    12→
    13→
    14→def _make_ws():
    15→    return Workspace({"/": MemoryBackend()}, mode=MountMode.WRITE)
    16→
    17→
    18→def test_local_commands_list():
    19→    reg = CommandRegistry()
    20→    acc = LocalCommandsAccessor(reg)
    21→    cmds = acc.list()
    22→    assert "ls" in cmds
    23→    assert "cat" in cmds
    24→
    25→
    26→def test_local_commands_register():
    27→    reg = CommandRegistry()
    28→    acc = LocalCommandsAccessor(reg)
    29→    acc.register("myjq", "jq .")
    30→    assert "myjq" in acc.list()
    31→
    32→
    33→def test_local_fuse_ops_list():
    34→    reg = FuseOpRegistry()
    35→    acc = LocalFuseOpsAccessor(reg)
    36→    ops = acc.list()
    37→    assert any(o["pattern"] == "*" for o in ops)
    38→
    39→
    40→def test_local_fuse_ops_register():
    41→    reg = FuseOpRegistry()
    42→    acc = LocalFuseOpsAccessor(reg)
    43→    acc.register("*.parquet", "parquet")
    44→    ops = acc.list()
    45→    assert any(o["pattern"] == "*.parquet" for o in ops)
    46→
    47→
    48→def test_local_fuse_ops_register_unknown():
    49→    reg = FuseOpRegistry()
    50→    acc = LocalFuseOpsAccessor(reg)
    51→    with pytest.raises(ValueError, match="unknown handler"):
    52→        acc.register("*.xyz", "nonexistent_handler_xyz")
    53→
    54→
    55→def test_local_workspace_ops_status():
    56→    ws = _make_ws()
    57→    acc = LocalWorkspaceOpsAccessor(ws)
    58→    result = acc.status()
    59→    assert isinstance(result, list)
    60→
    61→
    62→def test_local_workspace_ops_sync():
    63→    ws = _make_ws()
    64→    acc = LocalWorkspaceOpsAccessor(ws)
    65→    result = acc.sync()
    66→    assert isinstance(result, list)
    67→
    68→
    69→def test_local_workspace_ops_status_after_write():
    70→    ws = _make_ws()
    71→    ws.write("/f.txt", b"hello")
    72→    acc = LocalWorkspaceOpsAccessor(ws)
    73→    result = acc.status()
    74→    paths = [s["path"] for s in result]
    75→    assert "/f.txt" in paths
    76→
    77→
    78→def test_runtime_has_accessors():
    79→    ws = _make_ws()
    80→    rt = MemoryRuntime(ws)
    81→    assert hasattr(rt, "commands")
    82→    assert hasattr(rt, "fuse_ops")
    83→    assert hasattr(rt, "workspace")
    84→    assert "ls" in rt.commands.list()
    85→    assert isinstance(rt.fuse_ops.list(), list)
    86→    assert isinstance(rt.workspace.status(), list)
    87→
    88→
    89→def test_runtime_register_command_then_run():
    90→    ws = _make_ws()
    91→    rt = MemoryRuntime(ws)
    92→    rt.commands.register("hello", "echo hello")
    93→    assert "hello" in rt.commands.list()
    94→


     1→from mirage.cli.client import ClientError, MirageClient
     2→
     3→
     4→def test_mirage_client_imports():
     5→    assert MirageClient is not None
     6→    assert ClientError is not None
     7→


     1→import pytest
     2→
     3→from mirage.backend.local.local import LocalBackend
     4→from mirage.backend.local.memory import MemoryBackend
     5→from mirage.cli.config import (load_workspace_yaml, load_yaml_config,
     6→                               parse_mount_uri, parse_mount_uri_full,
     7→                               parse_mount_uri_with_mode,
     8→                               parse_mounts_from_cli, split_mount_spec)
     9→from mirage.types import ConsistencyPolicy, MountMode
    10→
    11→
    12→def test_parse_memory_uri():
    13→    backend = parse_mount_uri("memory")
    14→    assert isinstance(backend, MemoryBackend)
    15→
    16→
    17→def test_parse_local_uri():
    18→    backend = parse_mount_uri("local:./scratch")
    19→    assert isinstance(backend, LocalBackend)
    20→
    21→
    22→def test_parse_local_uri_relative_path():
    23→    backend = parse_mount_uri("./scratch")
    24→    assert isinstance(backend, LocalBackend)
    25→
    26→
    27→def test_parse_local_uri_absolute_path():
    28→    backend = parse_mount_uri("/tmp/mydir")
    29→    assert isinstance(backend, LocalBackend)
    30→
    31→
    32→def test_parse_unknown_scheme_raises():
    33→    with pytest.raises(ValueError, match="Unknown URI scheme"):
    34→        parse_mount_uri("memory://")
    35→
    36→
    37→def test_parse_mount_cli_args():
    38→    mounts = parse_mounts_from_cli(
    39→        ["memory:/data/", "local:./scratch:/local/"])
    40→    assert "/data/" in mounts
    41→    assert "/local/" in mounts
    42→    assert isinstance(mounts["/data/"], MemoryBackend)
    43→    assert isinstance(mounts["/local/"], LocalBackend)
    44→
    45→
    46→def test_load_yaml_config(tmp_path):
    47→    cfg_file = tmp_path / "ws.yaml"
    48→    cfg_file.write_text(
    49→        "mounts:\n  /data/: memory\n  /local/: local:./scratch\n"
    50→        "sync_policy: immediate\nmode: write\n")
    51→    cfg = load_yaml_config(str(cfg_file))
    52→    assert "/data/" in cfg["mounts"]
    53→    assert cfg["sync_policy"] == "immediate"
    54→    assert cfg["mode"] == "write"
    55→
    56→
    57→def test_parse_mount_uri_with_mode_read():
    58→    backend, mode = parse_mount_uri_with_mode("memory@read")
    59→    assert isinstance(backend, MemoryBackend)
    60→    assert mode == MountMode.READ
    61→
    62→
    63→def test_parse_mount_uri_with_mode_write():
    64→    backend, mode = parse_mount_uri_with_mode("memory@write")
    65→    assert isinstance(backend, MemoryBackend)
    66→    assert mode == MountMode.WRITE
    67→
    68→
    69→def test_parse_mount_uri_with_mode_default():
    70→    backend, mode = parse_mount_uri_with_mode("memory")
    71→    assert isinstance(backend, MemoryBackend)
    72→    assert mode == MountMode.READ
    73→
    74→
    75→def test_parse_mounts_from_cli_per_mount_mode():
    76→    mounts = parse_mounts_from_cli(
    77→        ["memory@write:/a/", "memory:/b/"],
    78→        default_mode=MountMode.READ,
    79→    )
    80→    assert isinstance(mounts["/a/"], tuple)
    81→    backend, mode = mounts["/a/"]
    82→    assert isinstance(backend, MemoryBackend)
    83→    assert mode == MountMode.WRITE
    84→    assert isinstance(mounts["/b/"], MemoryBackend)
    85→
    86→
    87→def test_split_mount_spec_memory():
    88→    backend, path = split_mount_spec("memory:/data/")
    89→    assert backend == "memory"
    90→    assert path == "/data/"
    91→
    92→
    93→def test_split_mount_spec_s3():
    94→    backend, path = split_mount_spec("s3://bucket:/")
    95→    assert backend == "s3://bucket"
    96→    assert path == "/"
    97→
    98→
    99→def test_split_mount_spec_with_mode():
   100→    backend, path = split_mount_spec("memory@write:/a/")
   101→    assert backend == "memory@write"
   102→    assert path == "/a/"
   103→
   104→
   105→def test_split_mount_spec_invalid():
   106→    with pytest.raises(ValueError, match="Invalid mount spec"):
   107→        split_mount_spec("nocolon")
   108→
   109→
   110→def test_load_workspace_yaml_per_mount_mode(tmp_path):
   111→    cfg_file = tmp_path / "ws.yaml"
   112→    cfg_file.write_text("mounts:\n"
   113→                        "  /data/: memory\n"
   114→                        "  /scratch/:\n"
   115→                        "    uri: memory\n"
   116→                        "    mode: write\n")
   117→    cfg = load_workspace_yaml(str(cfg_file))
   118→    assert cfg["mounts"]["/data/"] == "memory"
   119→    assert cfg["mounts"]["/scratch/"] == {"uri": "memory", "mode": "write"}
   120→
   121→
   122→def test_parse_mount_uri_full_lazy():
   123→    backend, mode, consistency = parse_mount_uri_full("memory@write#lazy")
   124→    assert isinstance(backend, MemoryBackend)
   125→    assert mode == MountMode.WRITE
   126→    assert consistency == ConsistencyPolicy.LAZY
   127→
   128→
   129→def test_parse_mount_uri_full_default():
   130→    backend, mode, consistency = parse_mount_uri_full("memory")
   131→    assert isinstance(backend, MemoryBackend)
   132→    assert mode == MountMode.READ
   133→    assert consistency == ConsistencyPolicy.NONE
   134→
   135→
   136→def test_load_workspace_yaml_consistency(tmp_path):
   137→    cfg_file = tmp_path / "ws.yaml"
   138→    cfg_file.write_text("consistency: lazy\n"
   139→                        "ttl: 60\n"
   140→                        "mounts:\n"
   141→                        "  /data/: memory\n"
   142→                        "  /scratch/:\n"
   143→                        "    uri: memory\n"
   144→                        "    consistency: always\n")
   145→    cfg = load_workspace_yaml(str(cfg_file))
   146→    assert cfg["consistency"] == "lazy"
   147→    assert cfg["ttl"] == 60
   148→    assert cfg["mounts"]["/data/"] == "memory"
   149→    assert cfg["mounts"]["/scratch/"] == {
   150→        "uri": "memory",
   151→        "consistency": "always",
   152→    }
   153→
   154→
   155→def test_load_workspace_yaml_custom_backend(tmp_path):
   156→    cfg_file = tmp_path / "ws.yaml"
   157→    cfg_file.write_text("mounts:\n"
   158→                        "  /data/:\n"
   159→                        "    backend: './my_backend.py:MyBackend'\n"
   160→                        "    args:\n"
   161→                        "      bucket: my-bucket\n"
   162→                        "    mode: write\n"
   163→                        "    consistency: lazy\n")
   164→    cfg = load_workspace_yaml(str(cfg_file))
   165→    mount = cfg["mounts"]["/data/"]
   166→    assert mount["backend"] == "./my_backend.py:MyBackend"
   167→    assert mount["args"] == {"bucket": "my-bucket"}
   168→    assert mount["mode"] == "write"
   169→    assert mount["consistency"] == "lazy"
   170→
   171→
   172→def test_load_workspace_yaml_custom_backend_minimal(tmp_path):
   173→    cfg_file = tmp_path / "ws.yaml"
   174→    cfg_file.write_text("mounts:\n"
   175→                        "  /data/:\n"
   176→                        "    backend: 'mypackage:MyBackend'\n")
   177→    cfg = load_workspace_yaml(str(cfg_file))
   178→    mount = cfg["mounts"]["/data/"]
   179→    assert mount == {"backend": "mypackage:MyBackend"}
   180→
   181→
   182→def test_load_workspace_yaml_with_fuse_ops(tmp_path):
   183→    cfg_file = tmp_path / "ws.yaml"
   184→    cfg_file.write_text("mounts:\n"
   185→                        "  /data/: memory\n"
   186→                        "fuse_ops:\n"
   187→                        "  '*.parquet': parquet\n"
   188→                        "  '*.csv': csv\n")
   189→    cfg = load_workspace_yaml(str(cfg_file))
   190→    assert "fuse_ops" in cfg
   191→    assert cfg["fuse_ops"]["*.parquet"] == "parquet"
   192→    assert cfg["fuse_ops"]["*.csv"] == "csv"
   193→
   194→
   195→def test_load_workspace_yaml_no_fuse_ops(tmp_path):
   196→    cfg_file = tmp_path / "ws.yaml"
   197→    cfg_file.write_text("mounts:\n  /data/: memory\n")
   198→    cfg = load_workspace_yaml(str(cfg_file))
   199→    assert "fuse_ops" not in cfg
   200→
   201→
   202→def test_load_workspace_yaml_remote(tmp_path):
   203→    cfg_file = tmp_path / "ws.yaml"
   204→    cfg_file.write_text("runtime: remote\n"
   205→                        "url: http://server:8000\n"
   206→                        "ws_id: abc12345\n"
   207→                        "mounts: {}\n")
   208→    cfg = load_workspace_yaml(str(cfg_file))
   209→    assert cfg["runtime"] == "remote"
   210→    assert cfg["url"] == "http://server:8000"
   211→    assert cfg["ws_id"] == "abc12345"
   212→
   213→
   214→def test_load_workspace_yaml_remote_with_command_url(tmp_path):
   215→    cfg_file = tmp_path / "ws.yaml"
   216→    cfg_file.write_text("runtime: remote\n"
   217→                        "url: http://data:8000\n"
   218→                        "ws_id: abc12345\n"
   219→                        "command_url: http://cmd:8001\n"
   220→                        "fuse_ops_url: http://ops:8002\n"
   221→                        "mounts: {}\n")
   222→    cfg = load_workspace_yaml(str(cfg_file))
   223→    assert cfg["runtime"] == "remote"
   224→    assert cfg["url"] == "http://data:8000"
   225→    assert cfg["command_url"] == "http://cmd:8001"
   226→    assert cfg["fuse_ops_url"] == "http://ops:8002"
   227→


     1→import os
     2→import subprocess
     3→import sys
     4→import tempfile
     5→import time
     6→from unittest.mock import patch
     7→
     8→import pytest
     9→
    10→
    11→def _run(args: list[str],
    12→         env: dict | None = None,
    13→         check: bool = True) -> subprocess.CompletedProcess:
    14→    e = os.environ.copy()
    15→    if env:
    16→        e.update(env)
    17→    return subprocess.run(
    18→        [sys.executable, "-m", "mirage.cli.app"] + args,
    19→        capture_output=True,
    20→        text=True,
    21→        env=e,
    22→        check=check,
    23→    )
    24→
    25→
    26→def test_help():
    27→    result = _run(["--help"], check=False)
    28→    assert result.returncode == 0
    29→
    30→
    31→def _fuse_defaults() -> dict:
    32→    from mirage.types import ConsistencyPolicy, MountMode, RuntimeType
    33→    return dict(
    34→        sync_policy="staged",
    35→        mode=MountMode.READ,
    36→        consistency=ConsistencyPolicy.NONE,
    37→        ttl=300,
    38→        config=None,
    39→        runtime=RuntimeType.MEMORY,
    40→        fuse=True,
    41→        agent_id=None,
    42→    )
    43→
    44→
    45→def test_mount_fuse_backend_mounts_at_root():
    46→    """--backend memory with --fuse mounts the backend at / prefix."""
    47→    from mirage.cli.commands.workspace import mount
    48→    with tempfile.TemporaryDirectory() as mp:
    49→        with patch("mirage.fuse.fs.mount") as mock_fuse, \
    50→             patch("mirage.cli.commands.workspace._ensure_server"), \
    51→             patch("mirage.cli.commands.workspace._client"):
    52→            mount(backend="memory", mountpoint=mp, **_fuse_defaults())
    53→            ws = mock_fuse.call_args.kwargs["fs"].ws
    54→            prefixes = [m.prefix for m in ws._registry._mounts]
    55→            assert "/" in prefixes
    56→
    57→
    58→def test_mount_fuse_backend_with_extra_spec():
    59→    """--backend combined with positional prefix:uri spec and --fuse."""
    60→    from mirage.cli.commands.workspace import mount
    61→    with tempfile.TemporaryDirectory() as mp:
    62→        with patch("mirage.fuse.fs.mount") as mock_fuse, \
    63→             patch("mirage.cli.commands.workspace._ensure_server"), \
    64→             patch("mirage.cli.commands.workspace._client"):
    65→            mount(["memory:/data/"],
    66→                  backend="memory",
    67→                  mountpoint=mp,
    68→                  **_fuse_defaults())
    69→            ws = mock_fuse.call_args.kwargs["fs"].ws
    70→            prefixes = [m.prefix for m in ws._registry._mounts]
    71→            assert "/" in prefixes
    72→            assert any(p.rstrip("/") == "/data" for p in prefixes)
    73→
    74→
    75→def test_mount_fuse_no_backend_no_spec_raises():
    76→    """mount --fuse with no --backend and no specs exits with error."""
    77→    result = _run(["mount", "--fuse", "--mountpoint", "/tmp/x"], check=False)
    78→    assert result.returncode != 0
    79→
    80→
    81→@pytest.mark.skip(reason="requires live server")
    82→def test_mount_exec_kill():
    83→    tmpdir = tempfile.mkdtemp(dir="/tmp")
    84→    env = {"MIRAGE_HOME": tmpdir}
    85→
    86→    result = _run(["mount", "memory:/data/"], env=env)
    87→    ws_id = result.stdout.strip()
    88→    assert len(ws_id) == 8
    89→
    90→    time.sleep(0.3)
    91→
    92→    result = _run(["exec", ws_id, "ls", "/data"], env=env)
    93→    assert result.returncode == 0
    94→
    95→    result = _run(["workspace", "ls"], env=env)
    96→    assert ws_id in result.stdout
    97→
    98→    _run(["kill", ws_id], env=env)
    99→    time.sleep(0.3)
   100→
   101→    result = _run(["workspace", "ls"], env=env, check=False)
   102→    assert ws_id not in result.stdout
   103→


     1→import pytest
     2→
     3→from mirage.commands.builtin.nav import cd, du, find, ls, pwd, stat, tree
     4→from mirage.types import FileStat, FileType
     5→
     6→
     7→def test_ls_basic(write_ws):
     8→    write_ws.write("/tmp/a.txt", b"x")
     9→    write_ws.write("/tmp/b.txt", b"y")
    10→    result = ls(write_ws, "/tmp/")
    11→    names = [e.name for e in result]
    12→    assert "a.txt" in names
    13→    assert "b.txt" in names
    14→
    15→
    16→def test_ls_returns_filestat(write_ws):
    17→    write_ws.write("/tmp/a.txt", b"hello")
    18→    result = ls(write_ws, "/tmp/")
    19→    assert len(result) >= 1
    20→    assert isinstance(result[0], FileStat)
    21→
    22→
    23→def test_ls_long_returns_filestat(write_ws):
    24→    write_ws.write("/tmp/a.txt", b"hello")
    25→    result = ls(write_ws, "/tmp/", long=True)
    26→    assert len(result) >= 1
    27→    assert isinstance(result[0], FileStat)
    28→
    29→
    30→def test_stat_file(write_ws):
    31→    write_ws.write("/tmp/f.txt", b"data")
    32→    s = stat(write_ws, "/tmp/f.txt")
    33→    assert s.name == "f.txt"
    34→    assert s.size == 4
    35→    assert s.type != FileType.DIRECTORY
    36→
    37→
    38→def test_stat_directory(write_ws):
    39→    write_ws.mkdir("/tmp/sub/")
    40→    s = stat(write_ws, "/tmp/sub/")
    41→    assert s.type == FileType.DIRECTORY
    42→
    43→
    44→def test_stat_missing_raises(write_ws):
    45→    with pytest.raises(FileNotFoundError):
    46→        stat(write_ws, "/tmp/missing.txt")
    47→
    48→
    49→def test_pwd_returns_cwd():
    50→    assert pwd("/some/path/") == "/some/path/"
    51→    assert pwd("/") == "/"
    52→
    53→
    54→def test_cd_absolute_path(write_ws):
    55→    write_ws.mkdir("/tmp/sub/")
    56→    result = cd(write_ws, "/", "/tmp/sub/")
    57→    assert result == "/tmp/sub/"
    58→
    59→
    60→def test_cd_relative_path(write_ws):
    61→    write_ws.mkdir("/tmp/sub/")
    62→    result = cd(write_ws, "/tmp/", "sub/")
    63→    assert result == "/tmp/sub/"
    64→
    65→
    66→def test_cd_dotdot(write_ws):
    67→    write_ws.mkdir("/tmp/sub/")
    68→    result = cd(write_ws, "/tmp/sub/", "..")
    69→    assert result == "/tmp"
    70→
    71→
    72→def test_cd_to_file_raises_not_a_directory(write_ws):
    73→    write_ws.write("/tmp/f.txt", b"data")
    74→    with pytest.raises(NotADirectoryError):
    75→        cd(write_ws, "/", "/tmp/f.txt")
    76→
    77→
    78→def test_find_all_files(write_ws):
    79→    write_ws.mkdir("/tmp/sub/")
    80→    write_ws.write("/tmp/a.txt", b"a")
    81→    write_ws.write("/tmp/sub/b.txt", b"b")
    82→    result = find(write_ws, "/tmp/")
    83→    assert any("a.txt" in p for p in result)
    84→    assert any("b.txt" in p for p in result)
    85→
    86→
    87→def test_find_by_name_glob(write_ws):
    88→    write_ws.write("/tmp/notes.txt", b"n")
    89→    write_ws.write("/tmp/data.csv", b"d")
    90→    result = find(write_ws, "/tmp/", name="*.txt")
    91→    assert any("notes.txt" in r for r in result)
    92→    assert not any("data.csv" in r for r in result)
    93→
    94→
    95→def test_find_by_type_file(write_ws):
    96→    write_ws.mkdir("/tmp/sub/")
    97→    write_ws.write("/tmp/f.txt", b"x")
    98→    write_ws.write("/tmp/sub/g.txt", b"y")
    99→    result = find(write_ws, "/tmp/", type="text")
   100→    assert len(result) >= 2
   101→    assert all("txt" in r for r in result)
   102→
   103→
   104→def test_find_by_type_directory(write_ws):
   105→    write_ws.mkdir("/tmp/sub/")
   106→    result = find(write_ws, "/tmp/", type=FileType.DIRECTORY)
   107→    assert any("sub" in r for r in result)
   108→
   109→
   110→def test_find_by_size_range(write_ws):
   111→    write_ws.write("/tmp/small.txt", b"hi")
   112→    write_ws.write("/tmp/large.txt", b"x" * 100)
   113→    result = find(write_ws, "/tmp/", min_size=10)
   114→    assert any("large.txt" in r for r in result)
   115→    assert not any("small.txt" in r for r in result)
   116→
   117→
   118→def test_tree_format_shows_connectors(write_ws):
   119→    write_ws.write("/tmp/a.txt", b"a")
   120→    write_ws.write("/tmp/b.txt", b"b")
   121→    result = tree(write_ws, "/tmp/")
   122→    assert any("\u251c\u2500\u2500" in line or "\u2514\u2500\u2500" in line
   123→               for line in result)
   124→
   125→
   126→def test_du_single_file(write_ws):
   127→    write_ws.write("/tmp/f.txt", b"hello")
   128→    result = du(write_ws, "/tmp/f.txt")
   129→    assert result == 5
   130→
   131→
   132→def test_du_directory_recursive(write_ws):
   133→    write_ws.mkdir("/tmp/sub/")
   134→    write_ws.write("/tmp/a.txt", b"aaa")
   135→    write_ws.write("/tmp/sub/b.txt", b"bb")
   136→    result = du(write_ws, "/tmp/")
   137→    assert result >= 5
   138→
   139→
   140→def test_du_missing_path_returns_zero(write_ws):
   141→    result = du(write_ws, "/tmp/nonexistent/")
   142→    assert result == 0
   143→


     1→import json
     2→
     3→import pytest
     4→
     5→from mirage.commands.builtin.search import cut, grep, jq, sort_lines, tr, uniq
     6→
     7→
     8→def test_grep_finds_matching_lines(write_ws):
     9→    write_ws.write("/tmp/f.txt", b"apple\nbanana\napricot")
    10→    result = grep(write_ws, "^ap", "/tmp/f.txt")
    11→    assert result == ["apple", "apricot"]
    12→
    13→
    14→def test_grep_case_insensitive(write_ws):
    15→    write_ws.write("/tmp/f.txt", b"Hello\nworld\nHELLO")
    16→    result = grep(write_ws, "hello", "/tmp/f.txt", ignore_case=True)
    17→    assert result == ["Hello", "HELLO"]
    18→
    19→
    20→def test_grep_invert_match(write_ws):
    21→    write_ws.write("/tmp/f.txt", b"apple\nbanana\napricot")
    22→    result = grep(write_ws, "^ap", "/tmp/f.txt", invert=True)
    23→    assert result == ["banana"]
    24→
    25→
    26→def test_grep_no_match_returns_empty(write_ws):
    27→    write_ws.write("/tmp/f.txt", b"hello\nworld")
    28→    result = grep(write_ws, "xyz", "/tmp/f.txt")
    29→    assert result == []
    30→
    31→
    32→def test_grep_recursive_finds_across_files(write_ws):
    33→    write_ws.mkdir("/tmp/sub/")
    34→    write_ws.write("/tmp/sub/one.txt", b"needle\nstraw")
    35→    write_ws.write("/tmp/sub/two.txt", b"straw\nneedle")
    36→    result = grep(write_ws, "needle", "/tmp/sub/", recursive=True)
    37→    assert any("needle" in r for r in result)
    38→
    39→
    40→def test_grep_recursive_flag(write_ws):
    41→    write_ws.mkdir("/tmp/sub/")
    42→    write_ws.write("/tmp/sub/f.txt", b"match\nskip")
    43→    result = grep(write_ws, "match", "/tmp/sub/", recursive=True)
    44→    assert any("match" in r for r in result)
    45→
    46→
    47→def test_jq_dot_returns_full_object(write_ws):
    48→    data = {"a": 1, "b": 2}
    49→    write_ws.write("/tmp/f.json", json.dumps(data).encode())
    50→    result = jq(write_ws, "/tmp/f.json", ".")
    51→    assert result == data
    52→
    53→
    54→def test_jq_key_access(write_ws):
    55→    write_ws.write("/tmp/f.json", b'{"name": "alice"}')
    56→    assert jq(write_ws, "/tmp/f.json", ".name") == "alice"
    57→
    58→
    59→def test_jq_nested_key(write_ws):
    60→    write_ws.write("/tmp/f.json", b'{"a": {"b": 42}}')
    61→    assert jq(write_ws, "/tmp/f.json", ".a.b") == 42
    62→
    63→
    64→def test_jq_array_iteration(write_ws):
    65→    write_ws.write("/tmp/f.json", b'{"items": [1, 2, 3]}')
    66→    result = jq(write_ws, "/tmp/f.json", ".items[]")
    67→    assert result == [1, 2, 3]
    68→
    69→
    70→def test_jq_invalid_expression_raises(write_ws):
    71→    write_ws.write("/tmp/f.json", b'{"a": 1}')
    72→    with pytest.raises(ValueError, match="unsupported jq"):
    73→        jq(write_ws, "/tmp/f.json", "a.b")
    74→
    75→
    76→def test_sort_lines_ascending(write_ws):
    77→    write_ws.write("/tmp/f.txt", b"banana\napple\ncherry")
    78→    result = sort_lines(write_ws, "/tmp/f.txt")
    79→    assert result == ["apple", "banana", "cherry"]
    80→
    81→
    82→def test_sort_lines_reverse(write_ws):
    83→    write_ws.write("/tmp/f.txt", b"banana\napple\ncherry")
    84→    result = sort_lines(write_ws, "/tmp/f.txt", reverse=True)
    85→    assert result == ["cherry", "banana", "apple"]
    86→
    87→
    88→def test_uniq_removes_consecutive_duplicates(write_ws):
    89→    write_ws.write("/tmp/f.txt", b"a\na\nb\nb\nc")
    90→    result = uniq(write_ws, "/tmp/f.txt")
    91→    assert result == ["a", "b", "c"]
    92→
    93→
    94→def test_uniq_non_consecutive_not_removed(write_ws):
    95→    write_ws.write("/tmp/f.txt", b"a\nb\na\nc\nb")
    96→    result = uniq(write_ws, "/tmp/f.txt")
    97→    assert result == ["a", "b", "a", "c", "b"]
    98→
    99→
   100→def test_cut_with_delimiter_and_fields(write_ws):
   101→    write_ws.write("/tmp/f.txt", b"a:b:c\nd:e:f")
   102→    result = cut(write_ws, "/tmp/f.txt", delimiter=":", fields=[1, 3])
   103→    assert result == ["a:c", "d:f"]
   104→
   105→
   106→def test_tr_translates_characters(write_ws):
   107→    write_ws.write("/tmp/f.txt", b"hello world")
   108→    result = tr(write_ws, "/tmp/f.txt", "aeiou", "AEIOU")
   109→    assert result == "hEllO wOrld"
   110→


     1→import hashlib
     2→
     3→import pytest
     4→
     5→from mirage.backend.local.local import LocalBackend
     6→from mirage.commands.builtin.read import cat, diff, file, head, md5, tail, wc
     7→from mirage.types import FileType, MountMode, SyncPolicy
     8→from mirage.workspace import Workspace
     9→
    10→
    11→@pytest.fixture
    12→def local_ws(tmp_path):
    13→    backend = LocalBackend(str(tmp_path))
    14→    return Workspace({"/": backend},
    15→                     mode=MountMode.WRITE,
    16→                     sync_policy=SyncPolicy.IMMEDIATE)
    17→
    18→
    19→# --- head ---
    20→
    21→
    22→def test_head_returns_first_n_lines(write_ws):
    23→    lines = b"\n".join(f"line{i}".encode() for i in range(20))
    24→    write_ws.write("/tmp/test.txt", lines)
    25→    result = head(write_ws, "/tmp/test.txt", lines=3)
    26→    assert result == b"line0\nline1\nline2"
    27→
    28→
    29→def test_head_default_10_lines(write_ws):
    30→    lines = b"\n".join(f"line{i}".encode() for i in range(20))
    31→    write_ws.write("/tmp/test.txt", lines)
    32→    result = head(write_ws, "/tmp/test.txt")
    33→    expected = b"\n".join(f"line{i}".encode() for i in range(10))
    34→    assert result == expected
    35→
    36→
    37→def test_head_empty_file(write_ws):
    38→    write_ws.write("/tmp/test.txt", b"")
    39→    assert head(write_ws, "/tmp/test.txt") == b""
    40→
    41→
    42→def test_head_fewer_lines_than_requested(write_ws):
    43→    write_ws.write("/tmp/test.txt", b"one\ntwo")
    44→    assert head(write_ws, "/tmp/test.txt", lines=10) == b"one\ntwo"
    45→
    46→
    47→def test_head_local_backend(local_ws):
    48→    local_ws.write("/hello.txt", b"a\nb\nc\nd")
    49→    assert head(local_ws, "/hello.txt", lines=2) == b"a\nb"
    50→
    51→
    52→# --- tail ---
    53→
    54→
    55→def test_tail_returns_last_n_lines(write_ws):
    56→    lines = b"\n".join(f"line{i}".encode() for i in range(20))
    57→    write_ws.write("/tmp/test.txt", lines)
    58→    result = tail(write_ws, "/tmp/test.txt", lines=3)
    59→    assert result == b"line17\nline18\nline19"
    60→
    61→
    62→def test_tail_default_10_lines(write_ws):
    63→    lines = b"\n".join(f"line{i}".encode() for i in range(20))
    64→    write_ws.write("/tmp/test.txt", lines)
    65→    result = tail(write_ws, "/tmp/test.txt")
    66→    expected = b"\n".join(f"line{i}".encode() for i in range(10, 20))
    67→    assert result == expected
    68→
    69→
    70→def test_tail_empty_file(write_ws):
    71→    write_ws.write("/tmp/test.txt", b"")
    72→    assert tail(write_ws, "/tmp/test.txt") == b""
    73→
    74→
    75→def test_tail_fewer_lines_than_requested(write_ws):
    76→    write_ws.write("/tmp/test.txt", b"one\ntwo")
    77→    assert tail(write_ws, "/tmp/test.txt", lines=10) == b"one\ntwo"
    78→
    79→
    80→def test_tail_local_backend(local_ws):
    81→    local_ws.write("/hello.txt", b"a\nb\nc\nd")
    82→    assert tail(local_ws, "/hello.txt", lines=2) == b"c\nd"
    83→
    84→
    85→# --- cat ---
    86→
    87→
    88→def test_cat_returns_bytes(write_ws):
    89→    write_ws.write("/tmp/f.txt", b"hello world")
    90→    assert cat(write_ws, "/tmp/f.txt") == b"hello world"
    91→
    92→
    93→def test_cat_empty_file(write_ws):
    94→    write_ws.write("/tmp/empty.txt", b"")
    95→    assert cat(write_ws, "/tmp/empty.txt") == b""
    96→
    97→
    98→def test_cat_local_backend(local_ws):
    99→    local_ws.write("/data.txt", b"from disk")
   100→    assert cat(local_ws, "/data.txt") == b"from disk"
   101→
   102→
   103→# --- wc ---
   104→
   105→
   106→def test_wc_counts(write_ws):
   107→    write_ws.write("/tmp/f.txt", b"one two\nthree\n")
   108→    result = wc(write_ws, "/tmp/f.txt")
   109→    assert result["lines"] == 2
   110→    assert result["words"] == 3
   111→    assert result["bytes"] == 14
   112→
   113→
   114→def test_wc_empty_file(write_ws):
   115→    write_ws.write("/tmp/empty.txt", b"")
   116→    result = wc(write_ws, "/tmp/empty.txt")
   117→    assert result == {"lines": 0, "words": 0, "bytes": 0}
   118→
   119→
   120→def test_wc_local_backend(local_ws):
   121→    local_ws.write("/f.txt", b"a b\nc\n")
   122→    result = wc(local_ws, "/f.txt")
   123→    assert result["lines"] == 2
   124→    assert result["words"] == 3
   125→
   126→
   127→# --- md5 ---
   128→
   129→
   130→def test_md5_matches_hashlib(write_ws):
   131→    data = b"hello"
   132→    write_ws.write("/tmp/f.txt", data)
   133→    assert md5(write_ws, "/tmp/f.txt") == hashlib.md5(data).hexdigest()
   134→
   135→
   136→def test_md5_empty_file(write_ws):
   137→    write_ws.write("/tmp/empty.txt", b"")
   138→    assert md5(write_ws, "/tmp/empty.txt") == hashlib.md5(b"").hexdigest()
   139→
   140→
   141→def test_md5_local_backend(local_ws):
   142→    data = b"disk content"
   143→    local_ws.write("/f.txt", data)
   144→    assert md5(local_ws, "/f.txt") == hashlib.md5(data).hexdigest()
   145→
   146→
   147→# --- diff ---
   148→
   149→
   150→def test_diff_identical_files(write_ws):
   151→    write_ws.write("/tmp/a.txt", b"same\n")
   152→    write_ws.write("/tmp/b.txt", b"same\n")
   153→    assert diff(write_ws, "/tmp/a.txt", "/tmp/b.txt") == []
   154→
   155→
   156→def test_diff_different_files(write_ws):
   157→    write_ws.write("/tmp/a.txt", b"hello\n")
   158→    write_ws.write("/tmp/b.txt", b"world\n")
   159→    result = diff(write_ws, "/tmp/a.txt", "/tmp/b.txt")
   160→    assert any("-hello" in line for line in result)
   161→    assert any("+world" in line for line in result)
   162→
   163→
   164→def test_diff_local_backend(local_ws):
   165→    local_ws.write("/a.txt", b"foo\n")
   166→    local_ws.write("/b.txt", b"bar\n")
   167→    result = diff(local_ws, "/a.txt", "/b.txt")
   168→    assert len(result) > 0
   169→
   170→
   171→# --- file / _detect ---
   172→
   173→
   174→def test_file_text(write_ws):
   175→    write_ws.write("/tmp/f.txt", b"hello world, plain text")
   176→    assert file(write_ws, "/tmp/f.txt") == FileType.TEXT
   177→
   178→
   179→def test_file_png(write_ws):
   180→    write_ws.write("/tmp/img.png", b"\x89PNGrest")
   181→    assert file(write_ws, "/tmp/img.png") == FileType.IMAGE_PNG
   182→
   183→
   184→def test_file_pdf(write_ws):
   185→    write_ws.write("/tmp/doc.pdf", b"%PDFrest")
   186→    assert file(write_ws, "/tmp/doc.pdf") == FileType.PDF
   187→
   188→
   189→def test_file_gzip(write_ws):
   190→    write_ws.write("/tmp/f.gz", b"\x1f\x8brest")
   191→    assert file(write_ws, "/tmp/f.gz") == FileType.GZIP
   192→
   193→
   194→def test_file_directory(write_ws):
   195→    write_ws.mkdir("/tmp/subdir/")
   196→    assert file(write_ws, "/tmp/subdir/") == FileType.DIRECTORY
   197→
   198→
   199→def test_filetype_is_str_compatible(write_ws):
   200→    write_ws.write("/tmp/f.txt", b"plain text here")
   201→    result = file(write_ws, "/tmp/f.txt")
   202→    assert result == "text"  # FileType.TEXT == "text" via str inheritance
   203→
   204→
   205→def test_file_local_backend(local_ws):
   206→    local_ws.write("/f.txt", b"plain text content here")
   207→    assert file(local_ws, "/f.txt") == "text"
   208→
   209→
   210→def test_presign_not_implemented_on_memory(write_ws):
   211→    write_ws.write("/tmp/test.txt", b"data")
   212→    with pytest.raises(NotImplementedError):
   213→        write_ws.backend("/tmp/test.txt").presign("/test.txt")
   214→