Add a smoke test for real servers, and show why transport errors happen
The suite runs against mock servers written from the upstream source, so
it cannot catch a place where a real server disagrees with that reading.
`examples/smoke_test.rs` can: it lists every resource, checks the
pagination walk against a single oversized request, probes the per_page
cap, and exercises statics, search and health_check. The upload wire
format (POST plus `_method=PUT`) was read from the framework source
rather than observed, so its write tier is the real verification of it.
It prints PASS/FAIL per check and never the contents of a record.
Writes are guarded. `IN_SMOKE_WRITE_HOST` must equal the host in
`IN_BASE_URL`, so a write can only reach a server that was named
explicitly. `IN_BASE_URL` has no default, unlike the other example,
because defaulting to the hosted app is the wrong failure for a tool
that can write. The write tier leaves a gap in the client number
sequence and a soft-deleted row even after cleanup, so it is documented
as disposable-instance only.
Running it against a dead port exposed a diagnosability gap:
`Error::Http` printed "error sending request for url (...)" and nothing
else. reqwest's Display stops at the outermost layer, and the reason
("Connection refused", a DNS failure, a TLS error) lives in `source()`.
The error now renders its whole cause chain.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1450,3 +1450,28 @@ async fn every_upload_method_targets_its_own_route() {
|
||||
.map(|resource| format!("/{resource}/i/upload"))
|
||||
);
|
||||
}
|
||||
|
||||
// --- transport error diagnosability -------------------------------------
|
||||
|
||||
/// `reqwest`'s own message stops at "error sending request for url (...)".
|
||||
/// The reason is in the source chain, and was invisible to anyone who just
|
||||
/// printed the error.
|
||||
#[tokio::test]
|
||||
async fn transport_errors_say_why_the_request_failed() {
|
||||
// Port 1 is reserved and nothing listens on it.
|
||||
let client = InvoiceNinjaClient::new("http://127.0.0.1:1/api/v1", "t")
|
||||
.unwrap()
|
||||
.with_retry_policy(RetryPolicy::none());
|
||||
|
||||
let err = client
|
||||
.list_clients(&ListParams::new())
|
||||
.await
|
||||
.expect_err("nothing is listening");
|
||||
|
||||
assert!(matches!(err, Error::Http(_)), "got {err:?}");
|
||||
let rendered = err.to_string().to_lowercase();
|
||||
assert!(
|
||||
rendered.contains("refused"),
|
||||
"the cause should appear in the message, got: {rendered}"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user