diff options
Diffstat (limited to 'packages/meshbay-hub/tests/test_no_index_cache.py')
| -rw-r--r-- | packages/meshbay-hub/tests/test_no_index_cache.py | 98 |
1 files changed, 98 insertions, 0 deletions
diff --git a/packages/meshbay-hub/tests/test_no_index_cache.py b/packages/meshbay-hub/tests/test_no_index_cache.py new file mode 100644 index 0000000..c39bf00 --- /dev/null +++ b/packages/meshbay-hub/tests/test_no_index_cache.py @@ -0,0 +1,98 @@ +""" +A group's index is never written to browser storage. + +`group_indexes` was an IndexedDB store holding a decrypted copy of every group's +index — each file's name, path, size, hash and uploader — written on every index +and every delta. The cross-group search of the time read it instead of dialling +anything, which is what it was for. + +Search has dialled the nodes since 2026-08-28. That change removed the reader and +kept the writers, so the browser went on building a cleartext file listing that +nothing consulted, that no sign-out removed (the key database is a different +one), and that grew with every group ever opened. **L7**, at rest. + +Showing a group's files while its node is unreachable is the only thing such a +cache buys, and it is not wanted: a listing you cannot open is worse than an +honest absence. So there is nothing left to read it with, and these tests keep it +that way — a writer reintroduced without a reader would be invisible again, and +the second time it would be invisible for the same reason as the first. +""" + +import re +from pathlib import Path + +STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static" +HUB_CLIENT = STATIC / "hub-client.js" +APP = STATIC / "app.js" + +# The store name, read from the source rather than written down here: renaming it +# must not quietly take these tests out of the picture. +STORE = re.search(r"const IDB_STORE = '([^']+)';", + HUB_CLIENT.read_text(encoding="utf-8")).group(1) + + +def _functions(src: str) -> dict[str, str]: + """Every top-level function in a module, by name.""" + out = {} + starts = [(m.start(), m.group(1)) for m in + re.finditer(r"^(?:async )?function (\w+)\(", src, re.M)] + for i, (at, name) in enumerate(starts): + end = starts[i + 1][0] if i + 1 < len(starts) else len(src) + out[name] = src[at:end] + return out + + +def test_only_the_purge_touches_the_old_store(): + """ + Creating it and emptying it, and nothing else. + + `openDB` still creates the store because dropping it needs a version bump, + and a version bump is an upgrade another tab can block — which would take + playlists down with it, since they share this database. An empty store costs + nothing; the point is that nothing writes to it. + """ + fns = _functions(HUB_CLIENT.read_text(encoding="utf-8")) + touching = sorted(n for n, body in fns.items() if "IDB_STORE" in body) + assert touching == ["openDB", "purgeGroupIndexCache"], ( + f"{touching} touch the {STORE!r} store; only creating and emptying it " + "are allowed, and a write to it is a file listing kept on disk that " + "nothing will ever read") + + +def test_nothing_writes_a_group_index_to_the_store(): + """Stated on the operation rather than on the callers, so a new one is caught.""" + fns = _functions(HUB_CLIENT.read_text(encoding="utf-8")) + purge = fns["purgeGroupIndexCache"] + assert ".clear()" in purge + for write in (".put(", ".add(", ".putAll("): + assert write not in purge, f"the purge does a {write} — it must only clear" + + +def test_no_module_carries_a_cache_writer_any_more(): + """ + The functions are gone, so the way this comes back is a new one. Any export + of hub-client.js whose name is about caching an index is refused here rather + than discovered months later with a store full of filenames. + """ + src = HUB_CLIENT.read_text(encoding="utf-8") + for name in re.findall(r"^(?:async )?function (\w+)\(", src, re.M): + assert not re.search(r"cache.*index|index.*cache", name, re.I) \ + or name == "purgeGroupIndexCache", ( + f"{name} looks like an index cache again — the store it would write " + "to has no reader, and adding one was decided against") + + +def test_the_purge_is_actually_called(): + """ + The defect being cleaned up was a function nobody called. A purge nobody + calls is the same defect wearing the opposite hat: the data stays on every + machine that already has it, and nothing says so. + """ + app = APP.read_text(encoding="utf-8") + assert "purgeGroupIndexCache" in app, "app.js no longer imports the purge" + call = re.search(r"purgeGroupIndexCache\(\)", app) + assert call, "the purge is imported and never called" + mount = app.index("const mount = () => {") + assert "purgeOnce()" in app[mount:mount + 400], ( + "the purge is no longer run at start-up, so a browser that still holds " + "the old store keeps it") |