fix(sync): bound request lifetime
req: operations/003
This commit is contained in:
@@ -599,7 +599,7 @@ async fn account_partition_hides_replay_and_export_until_owner_returns() -> WebD
|
||||
|
||||
#[tokio::test]
|
||||
async fn canonical_snapshot_and_history_are_tenant_scoped() -> WebDriverResult<()> {
|
||||
// test req: sync/007 req: security/004 req: auth/005
|
||||
// test req: sync/007 req: security/004 req: auth/005 req: performance/004
|
||||
let app_port = available_port();
|
||||
let app_addr = format!("127.0.0.1:{app_port}");
|
||||
let mut app_command = Command::new(env!("CARGO_BIN_EXE_hemx-kanban-example"));
|
||||
@@ -1708,7 +1708,7 @@ async fn missing_history_rebase_and_user_conflict_resolution_preserve_suffix() -
|
||||
#[tokio::test]
|
||||
async fn redacted_sync_diagnostics_are_bounded_and_leak_no_sensitive_material(
|
||||
) -> WebDriverResult<()> {
|
||||
// test req: operations/001 req: security/002 req: sync/016 req: sync/021
|
||||
// test req: operations/001 req: operations/005 req: security/002 req: sync/016 req: sync/021
|
||||
let app_port = available_port();
|
||||
let app_addr = format!("127.0.0.1:{app_port}");
|
||||
let mut app_command = Command::new(env!("CARGO_BIN_EXE_hemx-kanban-example"));
|
||||
@@ -2077,6 +2077,81 @@ async fn keep_local_retry_preserves_conflicted_command_and_suffix_order() -> Web
|
||||
result.and(quit)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn sync_requests_timeout_and_cancel_on_pagehide() -> WebDriverResult<()> {
|
||||
// test req: operations/003
|
||||
let app_port = available_port();
|
||||
let app_addr = format!("127.0.0.1:{app_port}");
|
||||
let mut app_command = Command::new(env!("CARGO_BIN_EXE_hemx-kanban-example"));
|
||||
app_command.env("HEMX_KANBAN_ADDR", &app_addr);
|
||||
let _app = TestProcess::start(app_command, "hemx-kanban", &app_addr, STARTUP_TIMEOUT)
|
||||
.expect("start hemx-kanban");
|
||||
|
||||
let webdriver_port = available_port();
|
||||
let webdriver_addr = format!("127.0.0.1:{webdriver_port}");
|
||||
let mut webdriver = Command::new("geckodriver");
|
||||
webdriver.arg("--port").arg(webdriver_port.to_string());
|
||||
let _webdriver = TestProcess::start(webdriver, "geckodriver", &webdriver_addr, STARTUP_TIMEOUT)
|
||||
.expect("start ready geckodriver");
|
||||
let mut caps = DesiredCapabilities::firefox();
|
||||
caps.set_headless()?;
|
||||
let driver = WebDriver::new(&format!("http://{webdriver_addr}"), caps).await?;
|
||||
|
||||
let result = async {
|
||||
driver.goto(&format!("http://{app_addr}/sync-demo")).await?;
|
||||
wait_until(
|
||||
&driver,
|
||||
"return document.querySelector('[data-kanban-sync]')?.getAttribute('data-sync-phase') === 'idle'",
|
||||
)
|
||||
.await?;
|
||||
let proof = driver
|
||||
.execute_async(
|
||||
r#"
|
||||
const done = arguments[arguments.length - 1];
|
||||
(async () => {
|
||||
const { fetchWithTimeout } = await import('/sync.js');
|
||||
const pendingFetch = (_input, init) => new Promise((_resolve, reject) => {
|
||||
init.signal.addEventListener('abort', () => reject(init.signal.reason), { once: true });
|
||||
});
|
||||
const started = performance.now();
|
||||
let timeout;
|
||||
try {
|
||||
await fetchWithTimeout('/never-timeout', {}, pendingFetch, 40);
|
||||
} catch (error) {
|
||||
timeout = { name: error.name, message: error.message, elapsedMs: performance.now() - started };
|
||||
}
|
||||
const cancellationPromise = fetchWithTimeout('/never-pagehide', {}, pendingFetch, 10_000)
|
||||
.then(() => ({ resolved: true }))
|
||||
.catch((error) => ({ name: error.name, message: error.message }));
|
||||
window.dispatchEvent(new PageTransitionEvent('pagehide'));
|
||||
done({ timeout, cancellation: await cancellationPromise });
|
||||
})().catch((error) => done({ error: String(error), stack: error?.stack }));
|
||||
"#,
|
||||
Vec::new(),
|
||||
)
|
||||
.await?
|
||||
.json()
|
||||
.clone();
|
||||
assert!(proof["error"].is_null(), "bounded request failed: {proof}");
|
||||
assert_eq!(proof["timeout"]["name"], "TimeoutError", "{proof}");
|
||||
assert!(proof["timeout"]["message"]
|
||||
.as_str()
|
||||
.is_some_and(|message| message.contains("40 ms")));
|
||||
assert!(proof["timeout"]["elapsedMs"]
|
||||
.as_f64()
|
||||
.is_some_and(|elapsed| (35.0..1_000.0).contains(&elapsed)));
|
||||
assert_eq!(proof["cancellation"]["name"], "AbortError", "{proof}");
|
||||
assert_eq!(
|
||||
proof["cancellation"]["message"],
|
||||
"sync cancelled because page is hidden"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
.await;
|
||||
let quit = driver.quit().await;
|
||||
result.and(quit)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn identical_sync_inputs_reconcile_deterministically() -> WebDriverResult<()> {
|
||||
// test req: sync/022
|
||||
|
||||
Reference in New Issue
Block a user