summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/tests/test_leaseless_reads.py
blob: 64aed3d348cbdeeb1dee080ae50f443580b509d8 (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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
"""
Browsing is never subject to a transfer slot — and is not unbounded either.

**Operator decision, 2026-09-08:** a member must be able to browse a group that
is at capacity exactly as they browse an idle one. Not the poster grid, not the
covers, not opening a photo or a PDF to look at it. §3.4 of
~/next/improve-downloads.md satisfies that structurally: a transfer is what the
transfers widget shows, and nothing else takes a slot.

But "not leased" cannot mean "unbounded". With MNP 3.0 making leases
compulsory, a client that simply omits `tr` would otherwise transfer outside
every cap, and the caps would be decoration — the leaseless branch left
reachable is finding C6's lesson (a transport that accepted a bare token) one
feature later.

So a leaseless read is bounded by a small count of *files in flight*, not by
bytes: a RAW photo out of a camera is 60–80 MB and is browsing, a 40 MB archive
is a download, and no size threshold separates them. What separates them is
which function asked.
"""

import re
from pathlib import Path

import pytest

from meshbay_node.transfers import (
    LEASELESS_IDLE_SECS, MAX_LEASELESS_IN_FLIGHT, LeaselessReads,
)

SPA = (Path(__file__).resolve().parents[2] / "meshbay-hub" / "src"
       / "meshbay_hub" / "static")


def test_a_viewer_looking_at_one_file_is_never_refused():
    reads = LeaselessReads()
    for chunk in range(20):
        assert reads.admit("photo-1", now=float(chunk)) is True


def test_a_second_file_is_allowed_so_prefetching_stays_possible():
    """One is what a viewer needs; two is so the photo viewer can fetch the
    next one while showing this one."""
    reads = LeaselessReads(limit=2)
    assert reads.admit("photo-1", now=0.0) is True
    assert reads.admit("photo-2", now=0.0) is True


def test_a_file_past_the_limit_is_refused():
    """The mechanism, at a limit this test sets itself: the shipped number is
    derived from the client and moves, and a test that hard-codes it is a test
    that breaks every time the client legitimately reads further ahead."""
    reads = LeaselessReads(limit=2)
    reads.admit("a", now=0.0)
    reads.admit("b", now=0.0)
    assert reads.admit("c", now=0.0) is False


def test_a_file_already_being_read_is_never_cut_off():
    """Even once the limit is reached. Refusing a chunk halfway through a photo
    because the count moved would be worse than never having admitted it — the
    viewer would show half an image and no error anyone can act on."""
    reads = LeaselessReads(limit=2)
    reads.admit("a", now=0.0)
    reads.admit("b", now=0.0)
    assert reads.admit("c", now=0.0) is False
    assert reads.admit("a", now=1.0) is True


def test_finishing_one_frees_it_at_once():
    """The last chunk is the only "close" a leaseless read has. Waiting for the
    idle timeout instead would mean somebody who looked at two photos cannot
    look at a third for a minute."""
    reads = LeaselessReads(limit=2)
    reads.admit("a", now=0.0)
    reads.admit("b", now=0.0)
    reads.finish("a")
    assert reads.admit("c", now=0.0) is True


def test_a_viewer_closed_mid_file_does_not_hold_its_place_for_ever():
    """It stops asking and says nothing — there is no message for "I closed the
    tab". Without the idle expiry the session would carry two dead entries and
    refuse every later preview, which is the bound turning into a bug."""
    reads = LeaselessReads(limit=2)
    reads.admit("a", now=0.0)
    reads.admit("b", now=0.0)
    assert reads.admit("c", now=1.0) is False
    assert reads.admit("c", now=LEASELESS_IDLE_SECS + 2) is True


def test_the_bound_covers_what_the_music_player_actually_reads_ahead():
    """The number is derived, not chosen — and choosing it is how it went wrong.

    §3.4.1 argued for two by reasoning about viewers: one photo, one document,
    plus one for prefetching the next. It forgot the music player, which warms a
    read-ahead window; playing an album on Wi-Fi therefore has six files in
    flight and the fourth was refused with `transfer_required`. Reported the day
    MNP 3.0 shipped as "I try to play a track and it tells me to download it
    instead" — a stated requirement (browsing is never subject to a slot) broken
    by a constant nobody had checked against the client.

    So this reads `prefetchDepth()` out of the shipped player and fails if the
    node's bound no longer covers it. Raising the client's read-ahead without
    raising the node's bound now breaks the build instead of reaching a person.
    """
    player = SPA / "music-player.js"
    if not player.exists():
        pytest.skip("the SPA sources are not present next to the node package")
    body = player.read_text()
    fn = body[body.index("function prefetchDepth()"):]
    fn = fn[:fn.index("\n}\n")]
    depths = [int(n) for n in re.findall(r"return (\d+);", fn)]
    assert depths, "prefetchDepth() no longer returns a number this can read"

    # The track playing, plus the widest read-ahead it will warm.
    music = max(depths) + 1
    # And a photo viewer with its own prefetch, in the same session: somebody
    # can look at photos while an album plays.
    viewer = 2
    assert MAX_LEASELESS_IN_FLIGHT >= music + viewer, (
        f"the music player reads {music} files ahead and the node admits only "
        f"{MAX_LEASELESS_IN_FLIGHT} leaseless — playing an album would be "
        "refused")


def test_the_bound_is_still_a_bound():
    """Generosity is cheap and refusal is not, but "unbounded" is the thing this
    exists to prevent: a client that omits `tr` must not have a download channel
    with no ceiling at all."""
    assert MAX_LEASELESS_IN_FLIGHT < 64