102 lines
3.7 KiB
Python
102 lines
3.7 KiB
Python
import json
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
|
|
ROOT = Path(__file__).parents[1]
|
|
STATUS = ROOT / "frontend" / "live-data-status.js"
|
|
HTML = ROOT / "frontend" / "index.html"
|
|
CSS = ROOT / "frontend" / "dashboard.css"
|
|
DASHBOARD = ROOT / "frontend" / "dashboard.js"
|
|
|
|
|
|
def run_node(script: str) -> dict:
|
|
result = subprocess.run(
|
|
["node", "-e", script], check=True, capture_output=True, text=True
|
|
)
|
|
return json.loads(result.stdout)
|
|
|
|
|
|
def test_live_data_status_summarizes_each_feed_without_claiming_live():
|
|
script = f"""
|
|
const status = require({json.dumps(str(STATUS))});
|
|
const healthy = {{fresh_for_seconds:8, sections:{{
|
|
context:{{age_seconds:1}}, notifications:{{age_seconds:2}}, events:{{age_seconds:3}}
|
|
}}}};
|
|
const oneDelayed = {{fresh_for_seconds:8, retry_in_seconds:30, sections:{{
|
|
context:{{age_seconds:1}}, notifications:{{age_seconds:14, stale:true, retry_in_seconds:30}}, events:{{age_seconds:3}}
|
|
}}}};
|
|
const twoDelayed = {{fresh_for_seconds:8, sections:{{
|
|
context:{{age_seconds:12, degraded:true}}, notifications:{{age_seconds:14, stale:true}}, events:{{age_seconds:3}}
|
|
}}}};
|
|
process.stdout.write(JSON.stringify({{
|
|
healthy:status.describe(healthy),
|
|
one:status.describe(oneDelayed),
|
|
two:status.describe(twoDelayed),
|
|
unavailable:status.describe({{}}),
|
|
}}));
|
|
"""
|
|
result = run_node(script)
|
|
|
|
assert result["healthy"]["summary"] == "Live"
|
|
assert [feed["state"] for feed in result["healthy"]["feeds"]] == ["live"] * 3
|
|
assert result["one"]["summary"] == "Updates delayed"
|
|
assert result["one"]["nextRetrySeconds"] == 30
|
|
assert result["one"]["feeds"][1] == {
|
|
"key": "notifications", "label": "Updates", "state": "delayed", "ageSeconds": 14
|
|
}
|
|
assert result["two"]["summary"] == "2 data feeds delayed"
|
|
assert result["unavailable"]["summary"] == "Live data unavailable"
|
|
|
|
|
|
def test_live_data_status_controller_is_single_flight_and_reports_result():
|
|
script = f"""
|
|
const status = require({json.dumps(str(STATUS))});
|
|
let resolveRefresh;
|
|
let calls = 0;
|
|
const states = [];
|
|
const button = {{disabled:false}};
|
|
const output = {{textContent:''}};
|
|
const controller = status.createRefreshController({{
|
|
button, output,
|
|
refresh:() => {{ calls += 1; return new Promise(resolve => {{ resolveRefresh = resolve; }}); }},
|
|
onState:value => states.push(value),
|
|
}});
|
|
(async () => {{
|
|
const first = controller.run();
|
|
const second = controller.run();
|
|
const pending = {{calls, disabled:button.disabled, text:output.textContent}};
|
|
resolveRefresh({{context:{{}}}});
|
|
await Promise.all([first, second]);
|
|
process.stdout.write(JSON.stringify({{pending, calls, disabled:button.disabled, text:output.textContent, states}}));
|
|
}})();
|
|
"""
|
|
|
|
assert run_node(script) == {
|
|
"pending": {"calls": 1, "disabled": True, "text": "Refreshing live data…"},
|
|
"calls": 1,
|
|
"disabled": False,
|
|
"text": "Live data refreshed.",
|
|
"states": ["refreshing", "success"],
|
|
}
|
|
|
|
|
|
def test_live_data_status_has_accessible_mobile_safe_sheet_contract():
|
|
html = HTML.read_text()
|
|
css = CSS.read_text()
|
|
dashboard = DASHBOARD.read_text()
|
|
|
|
assert 'id="open-live-data-status"' in html
|
|
assert 'aria-controls="live-data-status-sheet"' in html
|
|
assert 'id="live-data-status-sheet"' in html
|
|
assert 'aria-labelledby="live-data-status-heading"' in html
|
|
assert 'id="live-data-status-feeds"' in html
|
|
assert 'id="refresh-live-data"' in html
|
|
assert '<script src="static/live-data-status.js"></script>' in html
|
|
assert ".live-data-status-panel" in css
|
|
assert "width:min(560px,100%)" in css
|
|
assert "min-height:44px" in css
|
|
assert "liveDataStatus.describe" in dashboard
|
|
assert "liveDataStatus.createRefreshController" in dashboard
|
|
assert "contextPoller.getState()" in dashboard
|