|
@@ -2445,6 +2445,51 @@ def main():
|
|
|
check(wait_task_done(auth, race_task, timeout=60) is not None,
|
|
check(wait_task_done(auth, race_task, timeout=60) is not None,
|
|
|
"race forensics task completes")
|
|
"race forensics task completes")
|
|
|
|
|
|
|
|
|
|
+
|
|
|
|
|
+ print("== R11: live blocks arrive without a reload ==")
|
|
|
|
|
+ # The browser flow on each pulse: statum.act(task.refresh) — the
|
|
|
|
|
+ # sealed GET action must rehydrate the task slot with the new
|
|
|
|
|
+ # block. (The channel chain itself — entrypoint subscribe on
|
|
|
|
|
+ # load, SSE set events on bump — was verified end-to-end against
|
|
|
|
|
+ # a real channel client during the investigation.)
|
|
|
|
|
+ fake.state = "working"
|
|
|
|
|
+ fake.hold_state = True
|
|
|
|
|
+ act(auth, slot_public(auth, "/tasks", "tasks")["create"], {
|
|
|
|
|
+ "project_id": "1",
|
|
|
|
|
+ "title": "Live block task",
|
|
|
|
|
+ "priority": "2",
|
|
|
|
|
+ })
|
|
|
|
|
+ live_task = int([t["id"] for t in slot_public(auth, "/tasks", "tasks")["tasks"]
|
|
|
|
|
+ if t["title"] == "Live block task"][0])
|
|
|
|
|
+ check(wait_for(lambda: task_state(auth, live_task).get("status") == "working"
|
|
|
|
|
+ or False, timeout=60, what="live task working") is not None,
|
|
|
|
|
+ "live-block task reaches working")
|
|
|
|
|
+ conn = db()
|
|
|
|
|
+ live_session = conn.execute(
|
|
|
|
|
+ "select id from sessions where task_id = ?", (live_task,)).fetchone()[0]
|
|
|
|
|
+ conn.close()
|
|
|
|
|
+ status, _ = saddle_api("POST", "/api/saddle/%d/update" % fake_id, fake_token,
|
|
|
|
|
+ {"type": "block", "session_id": live_session, "data": {
|
|
|
|
|
+ "question": "Pick one", "type": "question",
|
|
|
|
|
+ "options": ["A", "B"]}})
|
|
|
|
|
+ check(status == 200, "block raised via the daemon")
|
|
|
|
|
+ check(wait_for(lambda: any(b.get("question") == "Pick one"
|
|
|
|
|
+ for b in task_state(auth, live_task).get("blocks", []))
|
|
|
|
|
+ or False, timeout=30, what="block lands in state") is not None,
|
|
|
|
|
+ "the raised block reaches the task state")
|
|
|
|
|
+ detail = task_state(auth, live_task)
|
|
|
|
|
+ check(detail.get("refresh") is not None,
|
|
|
|
|
+ "the sealed GET refresh action is still authored")
|
|
|
|
|
+ status, _ = http("GET", detail["refresh"]["uri"], timeout=15)
|
|
|
|
|
+ check(status in (200, 401),
|
|
|
|
|
+ "the refresh URI resolves as a GET (client roundtrip shape)")
|
|
|
|
|
+ act(auth, detail["answer_action"] if "answer_action" in detail
|
|
|
|
|
+ else detail["blocks"][0]["answer_action"], {"answer": "A"})
|
|
|
|
|
+ fake.hold_state = False
|
|
|
|
|
+ fake.state = "idle"
|
|
|
|
|
+ check(wait_task_done(auth, live_task, timeout=60) is not None,
|
|
|
|
|
+ "live-block task completes after answering")
|
|
|
|
|
+
|
|
|
# The daemon vanishing mid-task: three refused polls → offline,
|
|
# The daemon vanishing mid-task: three refused polls → offline,
|
|
|
# task requeued with a bot comment, UI reflects.
|
|
# task requeued with a bot comment, UI reflects.
|
|
|
act(auth, slot_public(auth, "/tasks", "tasks")["create"], {
|
|
act(auth, slot_public(auth, "/tasks", "tasks")["create"], {
|