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()); }