234 lines
7.9 KiB
Rust
234 lines
7.9 KiB
Rust
use wip_https::http::StatusCode;
|
|
use wip_https::wip_protocol::{
|
|
FetchInterfaceRequest, FetchInterfaceResponse, FetchRequest, INTERFACE_FORMAT_V1,
|
|
InterfaceDescriptor, Object, ProtocolError, ProtocolErrorCode,
|
|
};
|
|
use wip_https::{
|
|
BatchRequest, BatchRequestItem, BatchRequestKind, BatchResponse, BatchResponseItem,
|
|
BatchSuccess, ClientResponseError, DecodedResponse, Endpoint, InvalidResponseKind, Limits,
|
|
decode_batch_request, decode_batch_response, encode_batch_error_response, encode_batch_request,
|
|
encode_batch_response,
|
|
};
|
|
|
|
fn limits() -> Limits {
|
|
Limits::new(100_000, 100_000, 64, 4).unwrap()
|
|
}
|
|
|
|
fn request() -> BatchRequest {
|
|
BatchRequest {
|
|
requests: vec![
|
|
BatchRequestItem {
|
|
id: "object".into(),
|
|
request: BatchRequestKind::Fetch(FetchRequest {
|
|
path: "/item".into(),
|
|
}),
|
|
},
|
|
BatchRequestItem {
|
|
id: "interface".into(),
|
|
request: BatchRequestKind::FetchInterface(FetchInterfaceRequest {
|
|
interface: "interface-v1".into(),
|
|
}),
|
|
},
|
|
],
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn batch_round_trips_by_unique_id_with_item_failures_and_validators() {
|
|
let request = request();
|
|
let endpoint = Endpoint::parse("https://example.test/wip").unwrap();
|
|
let encoded_request = encode_batch_request(&endpoint, &request, limits()).unwrap();
|
|
assert_eq!(encoded_request.uri().path(), "/wip/v1/batch");
|
|
assert_eq!(
|
|
decode_batch_request(encoded_request.body(), limits()).unwrap(),
|
|
request
|
|
);
|
|
|
|
let response = BatchResponse {
|
|
responses: vec![
|
|
BatchResponseItem {
|
|
id: "interface".into(),
|
|
outcome: Err(ProtocolError {
|
|
code: ProtocolErrorCode::InterfaceNotFound,
|
|
message: "gone".into(),
|
|
}),
|
|
},
|
|
BatchResponseItem {
|
|
id: "object".into(),
|
|
outcome: Ok(BatchSuccess::Fetch(Object {
|
|
name: "item".into(),
|
|
description: None,
|
|
interfaces: vec![],
|
|
r#ref: None,
|
|
validator: Some(vec![0, 1, 2, 255]),
|
|
})),
|
|
},
|
|
],
|
|
};
|
|
let encoded = encode_batch_response(&request, &response, limits()).unwrap();
|
|
assert_eq!(encoded.status(), StatusCode::OK);
|
|
assert!(
|
|
std::str::from_utf8(encoded.body())
|
|
.unwrap()
|
|
.contains("AAEC/w==")
|
|
);
|
|
assert_eq!(
|
|
decode_batch_response(&request, &encoded, limits()).unwrap(),
|
|
DecodedResponse::Success(response)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn batch_interface_validator_round_trips_in_item_body() {
|
|
let request = BatchRequest {
|
|
requests: vec![BatchRequestItem {
|
|
id: "interface".into(),
|
|
request: BatchRequestKind::FetchInterface(FetchInterfaceRequest {
|
|
interface: "interface-v1".into(),
|
|
}),
|
|
}],
|
|
};
|
|
let response = BatchResponse {
|
|
responses: vec![BatchResponseItem {
|
|
id: "interface".into(),
|
|
outcome: Ok(BatchSuccess::FetchInterface(FetchInterfaceResponse {
|
|
interface: "interface-v1".into(),
|
|
descriptor: InterfaceDescriptor {
|
|
format: INTERFACE_FORMAT_V1.into(),
|
|
documentation: None,
|
|
types: vec![],
|
|
operations: vec![],
|
|
},
|
|
validator: Some(vec![255, 254, 253]),
|
|
})),
|
|
}],
|
|
};
|
|
let encoded = encode_batch_response(&request, &response, limits()).unwrap();
|
|
assert!(
|
|
std::str::from_utf8(encoded.body())
|
|
.unwrap()
|
|
.contains("//79")
|
|
);
|
|
assert_eq!(
|
|
decode_batch_response(&request, &encoded, limits()).unwrap(),
|
|
DecodedResponse::Success(response)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_batch_envelopes_are_rejected_at_top_level() {
|
|
let duplicate = br#"{
|
|
"requests": [
|
|
{"id":"same","method":"fetch","arguments":{"path":"/a"}},
|
|
{"id":"same","method":"fetch","arguments":{"path":"/b"}}
|
|
]
|
|
}"#;
|
|
assert!(decode_batch_request(duplicate, limits()).is_err());
|
|
|
|
let operation = br#"{
|
|
"requests": [
|
|
{"id":"call","method":"call_operation","arguments":{}}
|
|
]
|
|
}"#;
|
|
assert!(decode_batch_request(operation, limits()).is_err());
|
|
|
|
let too_many = Limits::new(100_000, 100_000, 64, 1).unwrap();
|
|
assert!(decode_batch_request(duplicate, too_many).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn top_level_batch_errors_use_the_batch_allowed_code_set() {
|
|
let invalid = ProtocolError {
|
|
code: ProtocolErrorCode::InvalidRequest,
|
|
message: "bad envelope".into(),
|
|
};
|
|
let encoded = encode_batch_error_response(&invalid, limits()).unwrap();
|
|
assert_eq!(
|
|
decode_batch_response(&request(), &encoded, limits()).unwrap(),
|
|
DecodedResponse::ProtocolFailure {
|
|
error: invalid,
|
|
status_mismatch: None,
|
|
}
|
|
);
|
|
|
|
let not_found = wip_https::http::Response::builder()
|
|
.status(StatusCode::NOT_FOUND)
|
|
.header("content-type", "application/json; charset=utf-8")
|
|
.body(br#"{"error":{"code":"not_found","message":"not a batch failure"}}"#.to_vec())
|
|
.unwrap();
|
|
assert!(matches!(
|
|
decode_batch_response(&request(), ¬_found, limits()).unwrap_err(),
|
|
ClientResponseError::InvalidResponse {
|
|
kind: InvalidResponseKind::InvalidBatch,
|
|
..
|
|
}
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn unsupported_batch_descriptor_waits_for_envelope_correlation() {
|
|
let duplicate = wip_https::http::Response::builder()
|
|
.status(StatusCode::OK)
|
|
.header("content-type", "application/json; charset=utf-8")
|
|
.body(
|
|
br#"{"responses":[{"id":"interface","result":{"interface":"interface-v1","descriptor":{"format":"wip-interface/2","future":true}}},{"id":"interface","result":{"interface":"interface-v1","descriptor":{"format":"wip-interface/2"}}}]}"#
|
|
.to_vec(),
|
|
)
|
|
.unwrap();
|
|
assert!(matches!(
|
|
decode_batch_response(&request(), &duplicate, limits()).unwrap_err(),
|
|
ClientResponseError::InvalidResponse {
|
|
kind: InvalidResponseKind::InvalidBatch,
|
|
..
|
|
}
|
|
));
|
|
|
|
let missing = wip_https::http::Response::builder()
|
|
.status(StatusCode::OK)
|
|
.header("content-type", "application/json; charset=utf-8")
|
|
.body(
|
|
br#"{"responses":[{"id":"interface","result":{"interface":"interface-v1","descriptor":{"format":"wip-interface/2"}}}]}"#
|
|
.to_vec(),
|
|
)
|
|
.unwrap();
|
|
assert!(matches!(
|
|
decode_batch_response(&request(), &missing, limits()).unwrap_err(),
|
|
ClientResponseError::InvalidResponse {
|
|
kind: InvalidResponseKind::InvalidBatch,
|
|
..
|
|
}
|
|
));
|
|
|
|
let request = BatchRequest {
|
|
requests: vec![BatchRequestItem {
|
|
id: "interface".into(),
|
|
request: BatchRequestKind::FetchInterface(FetchInterfaceRequest {
|
|
interface: "interface-v1".into(),
|
|
}),
|
|
}],
|
|
};
|
|
assert!(matches!(
|
|
decode_batch_response(&request, &missing, limits()).unwrap_err(),
|
|
ClientResponseError::UnsupportedDescriptorFormat { format }
|
|
if format == "wip-interface/2"
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn batch_response_must_have_exactly_the_requested_ids() {
|
|
let request = request();
|
|
let missing = BatchResponse {
|
|
responses: vec![BatchResponseItem {
|
|
id: "object".into(),
|
|
outcome: Ok(BatchSuccess::Fetch(Object {
|
|
name: "item".into(),
|
|
description: None,
|
|
interfaces: vec![],
|
|
r#ref: None,
|
|
validator: None,
|
|
})),
|
|
}],
|
|
};
|
|
assert!(encode_batch_response(&request, &missing, limits()).is_err());
|
|
}
|