|
|
@@ -1,4 +1,4 @@
|
|
|
-"""The push bundle (goal 47: rewritten for Spry.Push semantics; see
|
|
|
+"""The push bundle: Web Push over `Spry.Push` semantics (see
|
|
|
test/harness.py for the shared machinery and the runner)."""
|
|
|
from harness import * # noqa: F401,F403 — the shared wire helpers
|
|
|
|
|
|
@@ -26,13 +26,13 @@ def run(ctx):
|
|
|
finally:
|
|
|
conn.close()
|
|
|
|
|
|
- print("== task E: service worker asset ==")
|
|
|
+ print("== service worker asset ==")
|
|
|
status, sw = http("GET", "/sw.js", timeout=15)
|
|
|
check(status == 200 and "addEventListener('push'" in sw
|
|
|
and "notificationclick" in sw and "clients.openWindow" in sw,
|
|
|
"the service worker serves at the origin root with push + click routing")
|
|
|
|
|
|
- print("== goal 47: VAPID keys from the \"spry\" config section ==")
|
|
|
+ print("== VAPID keys from the \"spry\" config section ==")
|
|
|
# Spry owns the keys: `spry keys` writes the "spry" section the app
|
|
|
# reads — the app no longer generates or persists key material.
|
|
|
config = json.load(open(os.path.join(WORK, "web-config.json")))
|
|
|
@@ -53,7 +53,7 @@ def run(ctx):
|
|
|
check(dash.get("push_subscribed") is False and bool(dash.get("push_subscribe")),
|
|
|
"the bell starts unsubscribed and carries the subscribe action")
|
|
|
|
|
|
- print("== task E: subscription CRUD + auth validation ==")
|
|
|
+ print("== subscription CRUD + auth validation ==")
|
|
|
browser = ec.generate_private_key(ec.SECP256R1())
|
|
|
browser_public = browser.public_key().public_bytes(
|
|
|
Encoding.X962, PublicFormat.UncompressedPoint)
|
|
|
@@ -101,7 +101,7 @@ def run(ctx):
|
|
|
check(len(rows) == 1 and rows[0][0] == b64url(rotated_auth),
|
|
|
"re-subscribing an endpoint replaces its keys in place")
|
|
|
|
|
|
- print("== task E: notify-me pref persistence ==")
|
|
|
+ print("== notify-me pref persistence ==")
|
|
|
act(auth, slot_public(auth, "/tasks", "tasks")["create"],
|
|
|
{"project_id": "1", "title": "Push pref task", "priority": "5"})
|
|
|
pref_task = task_id_of("Push pref task")
|
|
|
@@ -122,7 +122,7 @@ def run(ctx):
|
|
|
check(task_state(auth, pref_task).get("notify_me") is False,
|
|
|
"the task slot reflects the opt-out")
|
|
|
|
|
|
- print("== task E: dispatch matrix — global blocks vs opted-in lifecycle ==")
|
|
|
+ print("== dispatch matrix — global blocks vs opted-in lifecycle ==")
|
|
|
# Two "browsers" for admin (/main, /gone) and one for another user
|
|
|
# (id 999, rows seeded directly — push targeting is per user).
|
|
|
endpoint_gone = push.url("/gone")
|
|
|
@@ -278,7 +278,7 @@ def run(ctx):
|
|
|
check(len(push.hits("/main")) == main_before2,
|
|
|
"an agent failure never reaches users without the pref")
|
|
|
|
|
|
- print("== goal 47: inline retry (Spry's bounded delivery) ==")
|
|
|
+ print("== inline retry (Spry's bounded delivery) ==")
|
|
|
# Spry's sender retries INLINE: two attempts, 250ms apart, then
|
|
|
# classifies. One transient 503 therefore lands on the second
|
|
|
# attempt moments later — no dispatcher tick, no queue row.
|
|
|
@@ -306,7 +306,7 @@ def run(ctx):
|
|
|
else:
|
|
|
check(False, "the inline retry fixture did not land")
|
|
|
|
|
|
- print("== goal 47: a hard-failing endpoint is RETRYABLE, never swept ==")
|
|
|
+ print("== a hard-failing endpoint is RETRYABLE, never swept ==")
|
|
|
# RFC 8030 §7 semantics (Spry): a persistent 503 exhausts the inline
|
|
|
# attempts and classifies RETRYABLE — the notification is NOT
|
|
|
# delivered, but the subscription row SURVIVES (only 401/403/404/410
|
|
|
@@ -425,7 +425,7 @@ def run(ctx):
|
|
|
"the goal success payload carries the goal title + click URL (%s)"
|
|
|
% done_goal_payload)
|
|
|
|
|
|
- print("== task E: unsubscribe removes the subscription ==")
|
|
|
+ print("== unsubscribe removes the subscription ==")
|
|
|
dash = slot_public(auth, "/", "dashboard")
|
|
|
check(dash.get("push_subscribed") is True,
|
|
|
"the bell shows subscribed while endpoints exist")
|