From 5062b907bd37e9578699dd9b0041149fdd655b09 Mon Sep 17 00:00:00 2001 From: osobh Date: Sat, 26 Sep 2026 18:23:27 -0500 Subject: [PATCH] clawhdf5-remote tests: the server counts only requests for its files A local port scanner's GET / reached the test listeners and was counted, failing the exact request budgets (and consuming injected 503s). Requests for paths the server does not serve are now answered 404 without being counted, delayed or failed; the query string is not part of the path. Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/clawhdf5-remote/tests/common/server.rs | 41 ++++++++++++------- crates/clawhdf5-remote/tests/http.rs | 18 ++++++++ 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/crates/clawhdf5-remote/tests/common/server.rs b/crates/clawhdf5-remote/tests/common/server.rs index 4749f50..a060a9d 100644 --- a/crates/clawhdf5-remote/tests/common/server.rs +++ b/crates/clawhdf5-remote/tests/common/server.rs @@ -3,7 +3,10 @@ //! and Last-Modified with `If-Match` / `If-Unmodified-Since` (412), and //! switches to misbehave — ignore ranges (200 with the whole file), cut //! bodies short, answer 503, respond slowly, send no validators. It counts -//! requests and body bytes, and logs every range asked for. +//! requests and body bytes, and logs every range asked for — only for the +//! paths it serves: a request for any other path (a local port scanner's +//! `GET /`, say) is answered 404 and not counted, so request budgets in +//! tests stay exact. #![allow(dead_code)] @@ -45,7 +48,8 @@ pub struct Shared { pub fail_next: AtomicU32, /// Sleep this long before answering each request. pub delay_ms: AtomicU64, - /// Requests served (every status). + /// Requests for a served path (every status); requests for other + /// paths are not counted. pub requests: AtomicU64, /// Body bytes sent. pub bytes: AtomicU64, @@ -223,14 +227,31 @@ fn serve(conn: TcpStream, s: &Shared) -> std::io::Result<()> { headers.insert(k.trim().to_ascii_lowercase(), v.trim().to_string()); } } + // The query string (a presigned URL's signature, say) is not part + // of the file's name. + let path = path.split('?').next().unwrap_or("").to_string(); + let close = headers + .get("connection") + .is_some_and(|v| v.eq_ignore_ascii_case("close")); + let res = { + let files = s.files.read().unwrap(); + files + .get(&path) + .map(|r| (r.data.clone(), r.etag.clone(), r.last_modified.clone())) + }; + let Some((data, etag, lm)) = res else { + // Not ours: not counted, not delayed, no failure injected. + write!(out, "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n")?; + if close { + return Ok(()); + } + continue; + }; s.requests.fetch_add(1, Ordering::SeqCst); let delay = s.delay_ms.load(Ordering::SeqCst); if delay > 0 { std::thread::sleep(Duration::from_millis(delay)); } - let close = headers - .get("connection") - .is_some_and(|v| v.eq_ignore_ascii_case("close")); if s.fail_next .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |n| n.checked_sub(1)) .is_ok() @@ -241,16 +262,6 @@ fn serve(conn: TcpStream, s: &Shared) -> std::io::Result<()> { )?; continue; } - let res = { - let files = s.files.read().unwrap(); - files - .get(&path) - .map(|r| (r.data.clone(), r.etag.clone(), r.last_modified.clone())) - }; - let Some((data, etag, lm)) = res else { - write!(out, "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n")?; - continue; - }; let len = data.len() as u64; let mut validators = String::new(); if s.weak_etag.load(Ordering::SeqCst) { diff --git a/crates/clawhdf5-remote/tests/http.rs b/crates/clawhdf5-remote/tests/http.rs index 5a68072..2c95cd1 100644 --- a/crates/clawhdf5-remote/tests/http.rs +++ b/crates/clawhdf5-remote/tests/http.rs @@ -526,3 +526,21 @@ fn bad_urls_and_statuses_are_clean_errors() { "{err}" ); } + +/// A request for a path the server does not serve (a local port scanner's +/// `GET /`) does not count against a test's request budget. +#[test] +fn requests_for_other_paths_are_not_counted() { + use std::io::{Read, Write}; + let server = Server::start(vec![("/m.h5".into(), multi_block_file())]); + let mut probe = std::net::TcpStream::connect(server.addr).unwrap(); + probe + .write_all(b"GET / HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n") + .unwrap(); + let mut answer = String::new(); + probe.read_to_string(&mut answer).unwrap(); + assert!(answer.starts_with("HTTP/1.1 404"), "{answer}"); + storage_for_url(&server.url("/m.h5"), &quick()).unwrap(); + assert_eq!(server.requests(), 1, "only the open counts"); + assert_eq!(server.log().len(), 1); +}