Two reports, one cause: the script's fetch had no .catch. When the server had exited the promise rejected, the chain never ran, and not even the status line moved — so a dead server and a click that did nothing were indistinguishable. play again read as a dead button, and the linger timeout was invisible. Now a rejection says the session is gone and seals the page, and a 5-second heartbeat against a new /alive notices it without needing a click, which is what the timeout case requires. The beat carries the token, and does not extend the linger: that deadline is absolute. The harness had the same hole. jsrun's fetch stub had no .catch, so the branch that notices a dead server would have been unreachable in every test — the very defect the stub's own comment records from CB-WP-0024. Teaching it __failing, .catch and a recorded setInterval was the fix; writing the script defensively would have repeated the trap. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
c2a7e40b67
commit
82aead6cea
9 changed files with 448 additions and 7 deletions
|
|
@ -52,6 +52,7 @@ pub use doc::{document, text_of, Endpoints};
|
|||
/// The endpoint pair every test in this crate posts to.
|
||||
#[cfg(test)]
|
||||
const TEST_ENDPOINTS: Endpoints<'static> = Endpoints {
|
||||
alive: "/alive?t=x",
|
||||
command: "/command?t=x",
|
||||
note: "/note?t=x",
|
||||
};
|
||||
|
|
@ -608,6 +609,84 @@ mod gamelog {
|
|||
}
|
||||
}
|
||||
|
||||
/// CB-WP-0035. A server that has gone must be NOTICED.
|
||||
///
|
||||
/// The script's `fetch` had no rejection handler, so when the process
|
||||
/// had exited the promise rejected and the chain never ran — not even
|
||||
/// the status line moved. Reported twice: *"play again does not
|
||||
/// report that the session is no longer available"*, and the linger
|
||||
/// timeout going unnoticed.
|
||||
///
|
||||
/// **Both from one cause**, so both are asserted: the click path and
|
||||
/// the heartbeat that needs no click.
|
||||
#[test]
|
||||
fn a_session_that_answers_nothing_is_reported_and_sealed() {
|
||||
let page = crate::doc::ending(
|
||||
None,
|
||||
"the game ended",
|
||||
"/command?t=x",
|
||||
None,
|
||||
"/alive?t=x",
|
||||
crate::doc::Account::of(&[]),
|
||||
&[],
|
||||
);
|
||||
for (beat, what) in [(false, "pressing a control"), (true, "the heartbeat")] {
|
||||
let (live, status) = crate::jsrun::unanswered(&page, beat).expect("run the page");
|
||||
assert!(
|
||||
status.contains("no longer available"),
|
||||
"{what} said nothing about the session: {status:?}"
|
||||
);
|
||||
assert!(
|
||||
status.contains("[sealed]"),
|
||||
"{what} left the page looking live: {status:?}"
|
||||
);
|
||||
assert!(
|
||||
live.is_empty(),
|
||||
"{what} left {live:?} still droppable on a dead session"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// **Both** pages must carry the heartbeat (CB-WP-0035).
|
||||
///
|
||||
/// The first cut wired it into the ending page only, and the gate
|
||||
/// caught it as an unused variable — which is luck, not a control.
|
||||
/// The timeout matters MOST during play: that is when a player walks
|
||||
/// away, and the table is the thing that must stop looking live.
|
||||
#[test]
|
||||
fn every_page_can_tell_the_session_has_gone() {
|
||||
let playing = document_with_log(
|
||||
&crate::testfix::view(Some(PlayerId(0))),
|
||||
&[],
|
||||
crate::TEST_ENDPOINTS,
|
||||
Some(PlayerId(0)),
|
||||
false,
|
||||
Account::of(&[]),
|
||||
&[],
|
||||
);
|
||||
let ended = crate::doc::ending(
|
||||
None,
|
||||
"m",
|
||||
"/command?t=x",
|
||||
None,
|
||||
"/alive?t=x",
|
||||
crate::doc::Account::of(&[]),
|
||||
&[],
|
||||
);
|
||||
for (name, page) in [("the table", &playing), ("the ending page", &ended)] {
|
||||
assert!(
|
||||
page.contains("CB_ALIVE"),
|
||||
"{name} cannot notice the session ending"
|
||||
);
|
||||
// With the token: an unauthenticated heartbeat is refused,
|
||||
// and a refusal every 5s would seal a LIVE session.
|
||||
assert!(
|
||||
page.contains("/alive?t="),
|
||||
"{name}'s heartbeat carries no token"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// CB-WP-0034. The move button must name **who**.
|
||||
///
|
||||
/// Reported three times across three sessions, two days apart, and it
|
||||
|
|
@ -1618,6 +1697,7 @@ mod ending_page {
|
|||
"the game ended",
|
||||
"/command?t=x",
|
||||
None,
|
||||
"/alive?t=x",
|
||||
crate::doc::Account::of(&[]),
|
||||
&[],
|
||||
)
|
||||
|
|
@ -1704,6 +1784,7 @@ mod ending_page {
|
|||
"m",
|
||||
"/command?t=x",
|
||||
None,
|
||||
"/alive?t=x",
|
||||
crate::doc::Account::of(&[]),
|
||||
&[],
|
||||
),
|
||||
|
|
@ -1762,6 +1843,7 @@ mod ending_page {
|
|||
"m",
|
||||
"/command?t=x",
|
||||
None,
|
||||
"/alive?t=x",
|
||||
crate::doc::Account::of(&[]),
|
||||
&[],
|
||||
))
|
||||
|
|
@ -1786,6 +1868,7 @@ mod ending_page {
|
|||
"P1 ran out of input",
|
||||
"/command?t=x",
|
||||
None,
|
||||
"/alive?t=x",
|
||||
crate::doc::Account::of(&[]),
|
||||
&[],
|
||||
));
|
||||
|
|
@ -1805,6 +1888,7 @@ mod ending_page {
|
|||
"m",
|
||||
"/command?t=x",
|
||||
None,
|
||||
"/alive?t=x",
|
||||
crate::doc::Account::of(&[]),
|
||||
&[],
|
||||
));
|
||||
|
|
@ -1835,6 +1919,7 @@ mod ending_page {
|
|||
"m",
|
||||
"/command?t=x",
|
||||
None,
|
||||
"/alive?t=x",
|
||||
crate::doc::Account::of(&[]),
|
||||
&[],
|
||||
));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue