From 7c240585d7b81b4c56ca6009ca5800ef65d249b3 Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Sun, 24 May 2026 21:51:14 +0200 Subject: [PATCH] test(serve): stabilize test_reload_yaml_error_returns_error_response MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same transient-connection-drop flake class that landed in fetch_page_with_retry (PR-train preceding v0.13.0). The raw TcpStream POST + read_to_end occasionally returns empty data on self-hosted-runner contention, leaving the HTTP status parsed as 0 and the assertion failing with "got status 0" — observed on PR #328 (no serve-code change in the PR, the failure was the test, not the production code). Extract a `post_reload_status` helper that retries once on `status == 0` after a 200 ms backoff (mirroring fetch_page_with_retry exactly), and use it from test_reload_yaml_error_returns_error_response. The other reload tests already work fine via fetch_with_timeout so they don't need the helper yet. Co-Authored-By: Claude Opus 4.7 --- rivet-cli/tests/serve_integration.rs | 66 +++++++++++++++++----------- 1 file changed, 41 insertions(+), 25 deletions(-) diff --git a/rivet-cli/tests/serve_integration.rs b/rivet-cli/tests/serve_integration.rs index a418de8..4ff6614 100644 --- a/rivet-cli/tests/serve_integration.rs +++ b/rivet-cli/tests/serve_integration.rs @@ -795,6 +795,46 @@ fn test_csp_header_present() { // ── STPA-Sec Section 12.4: Dashboard Reload Failure (H-16, SC-18) ───────── +/// POST /reload and parse the HTTP status from the response. Retries +/// once on `status == 0` — the same transient-connection-drop flake +/// class `fetch_page_with_retry` handles for GETs, observed under +/// CI runner load even though the server's health probe has already +/// passed. Used by the reload-flow tests. +fn post_reload_status(port: u16) -> u16 { + fn once(port: u16) -> u16 { + use std::io::{Read, Write}; + let Ok(mut stream) = std::net::TcpStream::connect(format!("127.0.0.1:{port}")) else { + return 0; + }; + let _ = stream.set_read_timeout(Some(Duration::from_secs(30))); + let request = format!( + "POST /reload HTTP/1.1\r\n\ + Host: 127.0.0.1:{port}\r\n\ + HX-Request: true\r\n\ + Content-Length: 0\r\n\ + Connection: close\r\n\r\n" + ); + if stream.write_all(request.as_bytes()).is_err() { + return 0; + } + let mut response = Vec::new(); + let _ = stream.read_to_end(&mut response); + let response = String::from_utf8_lossy(&response).to_string(); + response + .lines() + .next() + .and_then(|l| l.split_whitespace().nth(1)) + .and_then(|s| s.parse::().ok()) + .unwrap_or(0) + } + let first = once(port); + if first != 0 { + return first; + } + std::thread::sleep(Duration::from_millis(200)); + once(port) +} + // rivet: verifies SC-18, UCA-D-4 #[test] fn test_reload_yaml_error_returns_error_response() { @@ -802,31 +842,7 @@ fn test_reload_yaml_error_returns_error_response() { // a success response (200 or redirect) and not crash. let (mut child, port) = start_server(); - use std::io::{Read, Write}; - let mut stream = std::net::TcpStream::connect(format!("127.0.0.1:{port}")).expect("connect"); - stream - .set_read_timeout(Some(std::time::Duration::from_secs(30))) - .ok(); - - let request = format!( - "POST /reload HTTP/1.1\r\n\ - Host: 127.0.0.1:{port}\r\n\ - HX-Request: true\r\n\ - Content-Length: 0\r\n\ - Connection: close\r\n\r\n" - ); - stream.write_all(request.as_bytes()).expect("write reload"); - - let mut response = Vec::new(); - stream.read_to_end(&mut response).ok(); - let response = String::from_utf8_lossy(&response).to_string(); - - let status = response - .lines() - .next() - .and_then(|l| l.split_whitespace().nth(1)) - .and_then(|s| s.parse::().ok()) - .unwrap_or(0); + let status = post_reload_status(port); assert!( status == 200 || (300..400).contains(&status),