diff --git a/crates/buzz-admin/src/nip_fi.rs b/crates/buzz-admin/src/nip_fi.rs index 75eed82202..dd9a4867ec 100644 --- a/crates/buzz-admin/src/nip_fi.rs +++ b/crates/buzz-admin/src/nip_fi.rs @@ -277,11 +277,18 @@ mod postgres_tests { const ISS: &str = "https://issuer.example/api/goose"; + /// The production writer pool, with the connection budget of the sqlx + /// pools the other PostgreSQL tests use. The relay's 3 s acquire timeout + /// and two eager spare connections are sized for a warm relay pod, not for + /// the first wave of a test lane, where every worker clones its database + /// at once and a first connection can take longer than 3 s. async fn db() -> Db { let database_url = std::env::var("BUZZ_TEST_DATABASE_URL") .unwrap_or_else(|_| DbConfig::default().database_url); Db::new(&DbConfig { database_url, + min_connections: 0, + acquire_timeout_secs: 30, ..DbConfig::default() }) .await diff --git a/crates/buzz-agent/tests/fake_llm.rs b/crates/buzz-agent/tests/fake_llm.rs index 86c469f982..84dbf23c12 100644 --- a/crates/buzz-agent/tests/fake_llm.rs +++ b/crates/buzz-agent/tests/fake_llm.rs @@ -1521,17 +1521,33 @@ async fn steer_rejected_on_empty_prompt() { json!({"sessionId": sid, "expectedRunId": run_id, "prompt": []}), ) .await; - let mut saw_reject = false; + // The prompt runs in its own task while the steer is answered from the + // read loop, so the turn can finish before the steer is even read: wait + // for both responses in either order. + let (mut steer_reply, mut prompt_done) = (None, false); for _ in 0..40 { - let v = h.recv().await; - if v["id"] == json!(s_id) { - assert_eq!(v["error"]["code"], -32602, "empty prompt must be rejected"); - saw_reject = true; - } else if v["id"] == json!(p_id) { + if steer_reply.is_some() && prompt_done { break; } + let v = h.recv().await; + if v["id"] == json!(s_id) { + steer_reply = Some(v); + } else if v["id"] == json!(p_id) { + prompt_done = true; + } } - assert!(saw_reject, "empty steer prompt was not rejected"); + let steer_reply = steer_reply.expect("empty steer prompt got no response"); + assert_eq!( + steer_reply["error"]["code"], -32602, + "empty prompt must be rejected" + ); + // "no active run" and a run-id mismatch also answer -32602, and either can + // win if the turn ends first; the empty-prompt guard runs before both. + assert_eq!( + steer_reply["error"]["message"], "steer: prompt must not be empty", + "the empty-prompt guard must be what rejects the steer" + ); + assert!(prompt_done, "session/prompt did not finish"); h.shutdown().await; } diff --git a/crates/buzz-cli/src/client.rs b/crates/buzz-cli/src/client.rs index babf710b5a..9749c0ece1 100644 --- a/crates/buzz-cli/src/client.rs +++ b/crates/buzz-cli/src/client.rs @@ -3626,26 +3626,28 @@ mod desktop_throttle_tests { (BuzzClient::new(relay.into(), credentials).unwrap(), signed) } - /// A desktop allowing one sign-in signature per 300 ms against a relay - /// that drops unsigned sockets after 200 ms: the throttle is waited out - /// between connections, so only the live challenge is signed, with a - /// fresh assertion on the reconnect after the old one expired. + /// A desktop that throttles the first sign-in signature: the throttle is + /// waited out between connections, so only the live challenge is signed, + /// with a fresh assertion on the reconnect after the old one expired. + /// + /// The throttle is decided by call count, not by time since the test + /// started: on a loaded runner the desktop setup alone could outlast a + /// wall-clock throttle window, leaving nothing to wait out. The relay's + /// sign-in deadline is long for the same reason; the client drops the + /// throttled socket itself. #[tokio::test] async fn a_throttled_sign_in_reconnects_with_fresh_evidence() { use super::ws_deadline_tests::deadline_relay; let keys = nostr::Keys::generate(); - let (relay, conns, _, bearers) = - deadline_relay(std::time::Duration::from_millis(200)).await; - let last = std::sync::Mutex::new(std::time::Instant::now()); + let (relay, conns, _, bearers) = deadline_relay(std::time::Duration::from_secs(5)).await; + let signs = AtomicUsize::new(0); let (c, signed) = desktop(&relay, &keys, || dp::assertion(40), move |_| { - let mut last = last.lock().unwrap(); - if last.elapsed() < std::time::Duration::from_millis(300) { + if signs.fetch_add(1, Ordering::SeqCst) == 0 { crate::cached_assertion::test_clock::advance(60); return Some( serde_json::json!({"result": "", "error": "rate_limited", "retry_after": 30}), ); } - *last = std::time::Instant::now(); None }) .await;