78 lines
3.5 KiB
Python
78 lines
3.5 KiB
Python
import json
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
|
|
MODULE = Path(__file__).parents[1] / "frontend" / "push-notifications.js"
|
|
|
|
|
|
def run_scenario(script: str) -> dict:
|
|
harness = r"""
|
|
const createPushNotifications = require(__MODULE__);
|
|
const state = { prompts:0, requests:[], subscriptions:[], text:'' };
|
|
const control = {
|
|
checked:false, disabled:false,
|
|
addEventListener:(_name, callback) => state.change = callback,
|
|
};
|
|
const status = {set textContent(value) { state.text = value; }, get textContent() { return state.text; }};
|
|
const existing = {endpoint:'https://push.example/device', toJSON() { return {endpoint:this.endpoint, keys:{p256dh:'key',auth:'auth'}}; }};
|
|
const registration = {pushManager:{
|
|
getSubscription: async () => state.current || null,
|
|
subscribe: async options => { state.subscriptions.push(options); state.current=existing; return existing; },
|
|
}};
|
|
const feature = createPushNotifications({
|
|
control, status,
|
|
notification: {permission:'default', requestPermission:async () => { state.prompts += 1; return state.permission || 'granted'; }},
|
|
serviceWorker: {ready:Promise.resolve(registration)},
|
|
fetchJson: async (url, options={}) => { state.requests.push([url,options.method || 'GET',options.body || '']); return state.server || {available:true,subscribed:false,public_key:'AQID'}; },
|
|
});
|
|
(async () => { __SCENARIO__ })().catch(error => { console.error(error); process.exit(1); });
|
|
""".replace("__MODULE__", json.dumps(str(MODULE))).replace("__SCENARIO__", script)
|
|
completed = subprocess.run(
|
|
["node", "-e", harness], capture_output=True, check=True, text=True
|
|
)
|
|
return json.loads(completed.stdout)
|
|
|
|
|
|
def test_initialization_reports_availability_without_prompting_for_permission():
|
|
result = run_scenario("await feature.init(); process.stdout.write(JSON.stringify(state));")
|
|
|
|
assert result["prompts"] == 0
|
|
assert result["requests"] == [["api/v1/push-subscription", "GET", ""]]
|
|
assert result["text"] == "New update notifications are off for this device."
|
|
|
|
|
|
def test_user_gesture_subscribes_device_and_reports_enabled_state():
|
|
result = run_scenario("""
|
|
await feature.init();
|
|
control.checked = true;
|
|
await state.change();
|
|
process.stdout.write(JSON.stringify(state));
|
|
""")
|
|
|
|
assert result["prompts"] == 1
|
|
assert result["subscriptions"][0]["userVisibleOnly"] is True
|
|
assert result["requests"][-1][0:2] == ["api/v1/push-subscription", "PUT"]
|
|
assert json.loads(result["requests"][-1][2])["endpoint"] == "https://push.example/device"
|
|
assert result["text"] == "New update notifications enabled for this device."
|
|
|
|
|
|
def test_mobile_dashboard_mounts_opt_in_and_precaches_its_controller():
|
|
root = MODULE.parents[1]
|
|
html = (root / "frontend" / "index.html").read_text()
|
|
dashboard = (root / "frontend" / "dashboard.js").read_text()
|
|
worker = (root / "frontend" / "service-worker.js").read_text()
|
|
css = (root / "frontend" / "dashboard.css").read_text()
|
|
requirements = (root / "requirements.txt").read_text()
|
|
readme = (root / "README.md").read_text()
|
|
|
|
assert 'id="push-updates"' in html
|
|
assert 'id="push-update-status"' in html
|
|
assert '<script src="static/push-notifications.js"></script>' in html
|
|
assert "createPushNotifications({" in dashboard
|
|
assert "BASE + 'static/push-notifications.js'" in worker
|
|
assert ".push-update-control" in css and "min-height:44px" in css
|
|
assert "pywebpush==" in requirements
|
|
assert "STACKCHAIN_VAPID_PUBLIC_KEY" in readme
|
|
assert "STACKCHAIN_VAPID_PRIVATE_KEY" in readme
|