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
|
"""
Videos and Music show their cards a page at a time (`pager.js`).
The page size is a hub preference, so two things can go wrong without any
error on screen: a stored value the client misreads, and a key the hub refuses
— the Settings select would snap back and nothing would say why. The functions
are read out of `pager.js` rather than copied, and the key out of both files.
"""
import json
import re
import shutil
import subprocess
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub"
PAGER = ROOT / "static" / "pager.js"
USERS = ROOT / "api" / "users.py"
pytestmark = pytest.mark.skipif(
shutil.which("node") is None or not PAGER.exists(),
reason="node or the SPA sources are not available")
@pytest.fixture(scope="module")
def source():
text = PAGER.read_text(encoding="utf-8")
consts = re.findall(r"^export const PAGE_SIZE_\w+ = [^;]+;", text, re.M)
funcs = [re.search(rf"^export function {name}\(.*?^\}}", text, re.M | re.S)
for name in ("pageSizeFrom", "pageBounds")]
assert len(consts) == 4 and all(funcs), "pager.js no longer has what this test reads"
return "\n".join(consts + [m.group(0) for m in funcs]).replace("export ", "")
def _run(tmp_path, source, expr):
script = tmp_path / "case.js"
script.write_text(f"{source}\nconsole.log(JSON.stringify({expr}));")
out = subprocess.run(["node", str(script)], capture_output=True, text=True, check=True)
return json.loads(out.stdout)
@pytest.mark.parametrize("stored, expected", [
(None, 50), ("", 50), ("30", 30), ("10", 10), ("200", 200),
("0", 50), ("210", 50), ("35", 50), ("abc", 50), ("-10", 50),
])
def test_page_size_from_preference(tmp_path, source, stored, expected):
prefs = {} if stored is None else {"media_page_size": stored}
assert _run(tmp_path, source, f"pageSizeFrom({json.dumps(prefs)})") == expected
@pytest.mark.parametrize("total, size, page, expected", [
(0, 50, 0, {"page": 0, "last": 0, "start": 0, "end": 0}),
(120, 50, 0, {"page": 0, "last": 2, "start": 0, "end": 50}),
(120, 50, 2, {"page": 2, "last": 2, "start": 100, "end": 120}),
(100, 50, 1, {"page": 1, "last": 1, "start": 50, "end": 100}),
# The list shrank under the reader: the last page that exists, not an empty one.
(60, 50, 4, {"page": 1, "last": 1, "start": 50, "end": 60}),
(60, 50, -1, {"page": 0, "last": 1, "start": 0, "end": 50}),
])
def test_page_bounds(tmp_path, source, total, size, page, expected):
assert _run(tmp_path, source, f"pageBounds({total}, {size}, {page})") == expected
def test_hub_accepts_the_key_the_client_writes():
key = re.search(r"^export const PAGE_SIZE_PREF = '([^']+)';",
PAGER.read_text(encoding="utf-8"), re.M).group(1)
allowed = re.search(r"ALLOWED_PREF_KEYS = frozenset\(\[(.*?)\]\)",
USERS.read_text(encoding="utf-8"), re.S).group(1)
assert f'"{key}"' in allowed
|