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
|
"""
A browser signs itself out after a stretch with nobody at it (design §7.7).
`idle.js` is executed, not read: a fake document and localStorage stand in for
the browser, and a clock the test moves stands in for time. What is modelled is
the environment — the module under test is the real file. The wiring in app.js
is checked by reading it, the only evidence there is for the shell.
"""
import json
import re
import shutil
import subprocess
from pathlib import Path
import pytest
STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static"
IDLE = STATIC / "idle.js"
APP = STATIC / "app.js"
NODE = shutil.which("node") or ("/opt/nodejs/bin/node"
if Path("/opt/nodejs/bin/node").exists() else None)
HARNESS = r"""
const store = new Map();
globalThis.localStorage = {
getItem: (k) => (store.has(k) ? store.get(k) : null),
setItem: (k, v) => store.set(k, String(v)),
};
const media = [];
globalThis.document = {
querySelectorAll: () => media, addEventListener() {}, removeEventListener() {},
};
globalThis.window = { addEventListener() {}, removeEventListener() {} };
let now = 1_700_000_000_000;
Date.now = () => now;
const ticks = [];
globalThis.setInterval = (fn) => { ticks.push(fn); return ticks.length; };
globalThis.clearInterval = () => {};
const HOUR = 3600e3;
const { startIdleWatch, markActive, LAST_ACTIVE_KEY } = await import(process.argv[1]);
const out = {};
// Signed in just now, then left alone.
markActive(true);
let fired = 0;
const stop1 = startIdleWatch(HOUR, () => fired++);
now += HOUR - 1000; ticks.at(-1)(); out.justBefore = fired;
now += 2000; ticks.at(-1)(); out.justAfter = fired;
ticks.at(-1)(); out.firesOnce = fired;
stop1();
// A film nobody touches for two hours, then paused and left.
store.set(LAST_ACTIVE_KEY, String(now));
let filmFired = 0;
startIdleWatch(HOUR, () => filmFired++);
media.push({ paused: false, ended: false });
for (let i = 0; i < 120; i++) { now += 60e3; ticks.at(-1)(); }
out.duringFilm = filmFired;
media[0].paused = true;
now += HOUR + 60e3; ticks.at(-1)();
out.afterPause = filmFired;
media.length = 0;
// A browser closed without signing out and opened again the next day.
store.set(LAST_ACTIVE_KEY, String(now - 20 * HOUR));
let reopened = 0;
startIdleWatch(HOUR, () => reopened++);
out.reopened = reopened;
// A browser that has never recorded anything is not idle.
store.delete(LAST_ACTIVE_KEY);
let fresh = 0;
startIdleWatch(HOUR, () => fresh++);
out.noRecord = fresh;
console.log(JSON.stringify(out));
"""
@pytest.fixture(scope="module")
def outcome():
if NODE is None:
pytest.skip("node is not available")
proc = subprocess.run(
[NODE, "--input-type=module", "--eval", HARNESS, IDLE.as_uri()],
capture_output=True, text=True, timeout=30)
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout.strip().splitlines()[-1])
def test_nobody_at_it_for_the_delay_signs_out_once(outcome):
assert outcome["justBefore"] == 0
assert outcome["justAfter"] == 1
assert outcome["firesOnce"] == 1
def test_a_film_playing_is_somebody_watching(outcome):
assert outcome["duringFilm"] == 0, "two hours of film signed the browser out"
assert outcome["afterPause"] == 1, "a paused film kept the session forever"
def test_a_browser_closed_without_signing_out_is_caught_when_reopened(outcome):
assert outcome["reopened"] == 1
def test_a_browser_with_no_record_is_not_idle(outcome):
assert outcome["noRecord"] == 0
def test_the_desktop_application_is_not_watched():
src = APP.read_text(encoding="utf-8")
m = re.search(r"useEffect\(\(\) => \{\n(.*?)startIdleWatch\(", src, re.S)
assert m, "app.js no longer starts the idle watch in an effect"
assert "platform.isNative" in m.group(1)
def test_a_sign_in_resets_the_clock_before_the_session_lands():
src = APP.read_text(encoding="utf-8")
login = src[src.index("login: async (username, password)"):src.index("logout: () =>")]
assert login.index("markActive(true)") < login.index("setAuth(u)"), (
"the idle watch would read the previous user's last-active time")
def test_signing_out_revokes_on_the_hub_before_forgetting_the_token():
src = APP.read_text(encoding="utf-8")
logout = src[src.index("logout: () =>"):]
logout = logout[:logout.index("},")]
assert logout.index("logoutOnHub()") < logout.index("setAuth(null)"), (
"the refresh token is cleared before it can be sent for revocation")
|