1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
|
"""
Where a directory lives on the operator's disk is theirs, not the group's.
`RootSet.describe()` feeds two very different audiences. The index payload goes
to every member, and has always deliberately carried no paths — a member is
told what exists and whether it is readable, not that the library sits in
`/media/<the operator's name>/BACKUP2`. The loopback API answers the operator
themselves, over a channel that already requires being on their machine with
the run token, where the path is exactly what they are asking for.
`meshbay-node root list` printed `?` for every directory because it read a
field the member form omits. Nothing caught it: the CLI reads a dict, the
payload is a dict, and neither end says what keys it owes the other.
Both halves matter and they pull opposite ways, so both are asserted here — a
test that only checked the operator gets paths would be satisfied by putting
them in the member payload too.
"""
import inspect
import re
from pathlib import Path
from meshbay_node import daemon as daemon_mod
from meshbay_node import ops
from meshbay_node.roots import RootSet
def _roots(tmp_path: Path) -> RootSet:
for name in ("Films", "Albums"):
(tmp_path / name).mkdir()
return RootSet.build([
{"path": str(tmp_path / "Films"), "writable": True},
{"path": str(tmp_path / "Albums"), "removable": True},
])
# ── The member's half ────────────────────────────────────────────────────────
def test_the_default_form_carries_no_path(tmp_path):
described = _roots(tmp_path).describe()
assert described, "no roots described"
assert not any("path" in d for d in described), (
"the index payload every member receives would carry the operator's "
"filesystem layout")
def test_the_default_form_still_says_what_a_member_needs(tmp_path):
"""The counter-property: dropping the path must not drop the rest."""
described = _roots(tmp_path).describe()
for d in described:
assert set(d) >= {"name", "kind", "available", "writable",
"removable", "ejected"}
def test_the_index_payload_is_built_without_paths():
"""
Read from the source, because the alternative is asserting it about a
payload built by a test rather than by the node.
"""
from meshbay_node.indexer import indexer as indexer_mod
source = inspect.getsource(indexer_mod)
for call in re.findall(r"roots\.describe\([^)]*\)", source):
assert "with_paths" not in call, (
f"the indexer builds the member-facing roots table as {call} — "
f"that payload goes to everyone in the group")
# ── The operator's half ──────────────────────────────────────────────────────
def test_the_operator_form_carries_the_path(tmp_path):
described = _roots(tmp_path).describe(with_paths=True)
assert all(d.get("path") for d in described)
assert described[0]["path"] == str(tmp_path / "Films")
def test_the_loopback_api_asks_for_paths():
"""
`list_groups` answers the operator's own channel, and the CLI's `root list`
prints what it returns. Asking for the member form there is what printed a
column of question marks.
"""
source = inspect.getsource(ops.list_groups)
assert "describe(with_paths=True)" in source, (
"list_groups uses the member form, so every path it reports is missing")
def test_the_cli_only_reads_fields_the_payload_carries():
"""
The gap this whole file exists for. The CLI reads a dict and the API
returns a dict; nothing between them says which keys are owed, so a name
that is simply absent prints as a placeholder and looks like a node
problem.
"""
source = inspect.getsource(daemon_mod.main)
start = source.index('if args.command == "root":')
block = source[start:source.index('if args.command == "operator":', start)]
read = set(re.findall(r"r\.get\(['\"](\w+)['\"]", block))
read |= set(re.findall(r"r\[['\"](\w+)['\"]\]", block))
assert read, "the root CLI no longer reads the payload this way"
class _Any:
path = Path("/tmp/x")
name = "x"
kind = "generic"
writable = removable = ejected = False
available = True
offered = set(RootSet(roots=[_Any()]).describe(with_paths=True)[0])
assert read <= offered, (
f"the `root` CLI reads keys the loopback payload does not carry: "
f"{sorted(read - offered)}")
|