fix(http): separate transport and protocol limits

This commit is contained in:
2026-10-02 02:50:56 +09:00
parent 65c51006ad
commit df006ee706
12 changed files with 643 additions and 336 deletions
+106 -8
View File
@@ -145,16 +145,37 @@ fn valid_error_envelope_is_authoritative_and_status_mismatch_is_diagnostic() {
}
#[test]
fn non_success_without_valid_envelope_remains_transport_failure() {
fn resource_envelope_on_413_uses_code_and_records_canonical_422_mismatch() {
let (mut client, session) = setup();
let request = client.refresh_observed(&session, "/item", 0).unwrap();
let error = client
.complete(
request.id,
response(StatusCode::BAD_GATEWAY, b"gateway html"),
)
.unwrap_err();
assert_eq!(error.kind, ClientErrorKind::Transport);
let error = ProtocolError {
code: ProtocolErrorCode::ResourceLimitExceeded,
message: "logical observation limit exceeded".into(),
};
let mut encoded =
encode_protocol_error_response(ProtocolInteraction::Observe, &error, limits()).unwrap();
*encoded.status_mut() = StatusCode::PAYLOAD_TOO_LARGE;
assert_eq!(
client.complete(request.id, encoded).unwrap(),
Completion::ProtocolFailure(error)
);
assert_eq!(
client.diagnostics(&session).unwrap().back().unwrap().kind,
DiagnosticKind::HttpStatusMismatch
);
}
#[test]
fn non_success_without_valid_envelope_remains_transport_failure() {
for status in [StatusCode::BAD_GATEWAY, StatusCode::PAYLOAD_TOO_LARGE] {
let (mut client, session) = setup();
let request = client.refresh_observed(&session, "/item", 0).unwrap();
let error = client
.complete(request.id, response(status, b"infrastructure failure"))
.unwrap_err();
assert_eq!(error.kind, ClientErrorKind::Transport);
}
}
#[test]
@@ -250,3 +271,80 @@ fn malformed_call_success_after_dispatch_is_outcome_unknown_and_stales_object()
);
assert_eq!(client.in_flight_len(), 0);
}
#[test]
fn plain_413_after_call_dispatch_is_unknown_transport_outcome() {
let (mut client, session) = setup();
let observation = client.refresh_observed(&session, "/item", 0).unwrap();
client
.complete(
observation.id,
encode_observe_response(
&ObserveRequest {
path: "/item".into(),
depth: 0,
},
&ObjectObservation {
object: object(),
children: None,
},
limits(),
)
.unwrap(),
)
.unwrap();
let interface = client.prepare_interface(&session, "math").unwrap();
client
.complete(
interface.id,
encode_fetch_interface_response(
&FetchInterfaceRequest {
interface: "math".into(),
},
&FetchInterfaceResponse {
interface: "math".into(),
descriptor: descriptor(INTERFACE_FORMAT_V1),
validator: None,
},
limits(),
)
.unwrap(),
)
.unwrap();
let call = client
.prepare_call(
&session,
"/item",
"math",
"calculate",
BTreeMap::from([("value".into(), Value::Integer(2))]),
)
.unwrap();
client.mark_dispatched(call.id).unwrap();
let completion = client
.complete(
call.id,
Response::builder()
.status(StatusCode::PAYLOAD_TOO_LARGE)
.body(Vec::<u8>::new())
.unwrap(),
)
.unwrap();
assert!(matches!(
completion,
Completion::Call(record)
if matches!(
&record.outcome,
CallOutcome::Unknown {
reason: OutcomeUnknownReason::ResponseDecode,
failure: Some(failure),
} if failure.kind == ClientErrorKind::Transport
)
));
assert_eq!(
client.object(&session, "/item").unwrap().state,
ObservationState::Stale
);
assert_eq!(client.in_flight_len(), 0);
}