aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests/test_no_index_cache.py
blob: c39bf003d7a699d7e74a210c03d5a6bcb72530f0 (plain) (blame)
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
"""
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")