feat: unify object observation API
This commit is contained in:
@@ -6,12 +6,13 @@ use wip_client::{
|
||||
};
|
||||
use wip_https::http::{Response, StatusCode};
|
||||
use wip_https::{
|
||||
Limits, encode_fetch_interface_response, encode_fetch_response, encode_protocol_error_response,
|
||||
Limits, encode_fetch_interface_response, encode_observe_response,
|
||||
encode_protocol_error_response,
|
||||
};
|
||||
use wip_protocol::{
|
||||
FetchInterfaceRequest, FetchInterfaceResponse, FetchRequest, INTERFACE_FORMAT_V1,
|
||||
InterfaceDescriptor, Object, OperationDeclaration, ParameterDeclaration, ProtocolError,
|
||||
ProtocolErrorCode, ProtocolInteraction, ReturnDeclaration, TypeExpr, Value,
|
||||
FetchInterfaceRequest, FetchInterfaceResponse, INTERFACE_FORMAT_V1, InterfaceDescriptor,
|
||||
Object, ObjectObservation, ObserveRequest, OperationDeclaration, ParameterDeclaration,
|
||||
ProtocolError, ProtocolErrorCode, ProtocolInteraction, ReturnDeclaration, TypeExpr, Value,
|
||||
};
|
||||
|
||||
fn limits() -> Limits {
|
||||
@@ -76,7 +77,7 @@ fn malformed_json_unknown_fields_duplicates_and_invalid_success_are_invalid_resp
|
||||
];
|
||||
for body in cases {
|
||||
let (mut client, session) = setup();
|
||||
let request = client.prepare_fetch(&session, "/item").unwrap();
|
||||
let request = client.prepare_observe(&session, "/item", 0).unwrap();
|
||||
let error = client
|
||||
.complete(request.id, response(StatusCode::OK, body))
|
||||
.unwrap_err();
|
||||
@@ -106,7 +107,7 @@ fn disallowed_code_and_status_success_contradictions_are_invalid_response() {
|
||||
];
|
||||
for (status, body) in cases {
|
||||
let (mut client, session) = setup();
|
||||
let request = client.prepare_fetch(&session, "/item").unwrap();
|
||||
let request = client.prepare_observe(&session, "/item", 0).unwrap();
|
||||
assert_eq!(
|
||||
client
|
||||
.complete(request.id, response(status, body))
|
||||
@@ -120,13 +121,13 @@ fn disallowed_code_and_status_success_contradictions_are_invalid_response() {
|
||||
#[test]
|
||||
fn valid_error_envelope_is_authoritative_and_status_mismatch_is_diagnostic() {
|
||||
let (mut client, session) = setup();
|
||||
let request = client.prepare_fetch(&session, "/item").unwrap();
|
||||
let request = client.prepare_observe(&session, "/item", 0).unwrap();
|
||||
let error = ProtocolError {
|
||||
code: ProtocolErrorCode::NotFound,
|
||||
message: "missing".into(),
|
||||
};
|
||||
let mut encoded =
|
||||
encode_protocol_error_response(ProtocolInteraction::Fetch, &error, limits()).unwrap();
|
||||
encode_protocol_error_response(ProtocolInteraction::Observe, &error, limits()).unwrap();
|
||||
*encoded.status_mut() = StatusCode::IM_A_TEAPOT;
|
||||
|
||||
assert_eq!(
|
||||
@@ -146,7 +147,7 @@ fn valid_error_envelope_is_authoritative_and_status_mismatch_is_diagnostic() {
|
||||
#[test]
|
||||
fn non_success_without_valid_envelope_remains_transport_failure() {
|
||||
let (mut client, session) = setup();
|
||||
let request = client.prepare_fetch(&session, "/item").unwrap();
|
||||
let request = client.prepare_observe(&session, "/item", 0).unwrap();
|
||||
let error = client
|
||||
.complete(
|
||||
request.id,
|
||||
@@ -176,15 +177,19 @@ fn unsupported_descriptor_format_is_distinct_from_invalid_response() {
|
||||
#[test]
|
||||
fn malformed_call_success_after_dispatch_is_outcome_unknown_and_stales_object() {
|
||||
let (mut client, session) = setup();
|
||||
let fetch = client.prepare_fetch(&session, "/item").unwrap();
|
||||
let observation = client.prepare_observe(&session, "/item", 0).unwrap();
|
||||
client
|
||||
.complete(
|
||||
fetch.id,
|
||||
encode_fetch_response(
|
||||
&FetchRequest {
|
||||
observation.id,
|
||||
encode_observe_response(
|
||||
&ObserveRequest {
|
||||
path: "/item".into(),
|
||||
depth: 0,
|
||||
},
|
||||
&ObjectObservation {
|
||||
object: object(),
|
||||
children: None,
|
||||
},
|
||||
&object(),
|
||||
limits(),
|
||||
)
|
||||
.unwrap(),
|
||||
|
||||
+271
-140
@@ -6,20 +6,54 @@ use wip_client::{
|
||||
};
|
||||
use wip_https::http::Response;
|
||||
use wip_https::{
|
||||
Limits, encode_call_operation_response, encode_fetch_interface_response, encode_fetch_response,
|
||||
encode_fetch_tree_response, encode_protocol_error_response,
|
||||
EncodeHttpError, Limits, encode_call_operation_response, encode_fetch_interface_response,
|
||||
encode_observe_response, encode_protocol_error_response,
|
||||
};
|
||||
use wip_protocol::{
|
||||
CallOperationResponse, FetchInterfaceRequest, FetchInterfaceResponse, FetchRequest,
|
||||
FetchTreeRequest, FetchTreeResponse, INTERFACE_FORMAT_V1, InterfaceDescriptor, Object,
|
||||
ObjectTreeNode, OperationDeclaration, ParameterDeclaration, ProtocolError, ProtocolErrorCode,
|
||||
ProtocolInteraction, ReturnDeclaration, TypeExpr, Value,
|
||||
CallOperationResponse, FetchInterfaceRequest, FetchInterfaceResponse, INTERFACE_FORMAT_V1,
|
||||
InterfaceDescriptor, Object, ObjectObservation, ObserveRequest, OperationDeclaration,
|
||||
ParameterDeclaration, ProtocolError, ProtocolErrorCode, ProtocolInteraction, ReturnDeclaration,
|
||||
TypeExpr, Value,
|
||||
};
|
||||
|
||||
fn wire_limits() -> Limits {
|
||||
Limits::new(64 * 1024, 64 * 1024, 64, 64).unwrap()
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct TestObservation {
|
||||
object: Object,
|
||||
children: Vec<TestObservation>,
|
||||
}
|
||||
|
||||
struct TestObservationResponse {
|
||||
root: TestObservation,
|
||||
}
|
||||
|
||||
fn protocol_observation(node: &TestObservation, depth: u32) -> ObjectObservation {
|
||||
ObjectObservation {
|
||||
object: node.object.clone(),
|
||||
children: (depth > 0).then(|| {
|
||||
node.children
|
||||
.iter()
|
||||
.map(|child| protocol_observation(child, depth - 1))
|
||||
.collect()
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn encode_test_observation_response(
|
||||
request: &ObserveRequest,
|
||||
value: &TestObservationResponse,
|
||||
limits: Limits,
|
||||
) -> Result<Response<Vec<u8>>, EncodeHttpError> {
|
||||
encode_observe_response(
|
||||
request,
|
||||
&protocol_observation(&value.root, request.depth),
|
||||
limits,
|
||||
)
|
||||
}
|
||||
|
||||
fn client(objects: usize, interfaces: usize, in_flight: usize, history: usize) -> Client {
|
||||
Client::new(
|
||||
ClientLimits::new(8, objects, interfaces, in_flight, history).unwrap(),
|
||||
@@ -59,14 +93,25 @@ fn descriptor(return_type: TypeExpr) -> InterfaceDescriptor {
|
||||
}
|
||||
}
|
||||
|
||||
fn fetch_response(path: &str, value: &Object) -> Response<Vec<u8>> {
|
||||
encode_fetch_response(&FetchRequest { path: path.into() }, value, wire_limits()).unwrap()
|
||||
fn observation_response(path: &str, value: &Object) -> Response<Vec<u8>> {
|
||||
encode_observe_response(
|
||||
&ObserveRequest {
|
||||
path: path.into(),
|
||||
depth: 0,
|
||||
},
|
||||
&ObjectObservation {
|
||||
object: value.clone(),
|
||||
children: None,
|
||||
},
|
||||
wire_limits(),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn observe_object(client: &mut Client, session: &wip_client::SessionId, path: &str, value: Object) {
|
||||
let request = client.prepare_fetch(session, path).unwrap();
|
||||
let request = client.prepare_observe(session, path, 0).unwrap();
|
||||
client
|
||||
.complete(request.id, fetch_response(path, &value))
|
||||
.complete(request.id, observation_response(path, &value))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
@@ -134,14 +179,14 @@ fn newer_refresh_rejects_out_of_order_response_for_the_same_subject() {
|
||||
let session = client
|
||||
.open_session("https://example.test/wip", SecurityContext::new("subject"))
|
||||
.unwrap();
|
||||
let old = client.prepare_fetch(&session, "/item").unwrap();
|
||||
let new = client.prepare_fetch(&session, "/item").unwrap();
|
||||
let old = client.prepare_observe(&session, "/item", 0).unwrap();
|
||||
let new = client.prepare_observe(&session, "/item", 0).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
client
|
||||
.complete(
|
||||
old.id,
|
||||
fetch_response("/item", &object("item", None, b"old"))
|
||||
observation_response("/item", &object("item", None, b"old"))
|
||||
)
|
||||
.unwrap(),
|
||||
Completion::StaleResponseRejected(old.id)
|
||||
@@ -154,7 +199,7 @@ fn newer_refresh_rejects_out_of_order_response_for_the_same_subject() {
|
||||
client
|
||||
.complete(
|
||||
new.id,
|
||||
fetch_response("/item", &object("item", None, b"new")),
|
||||
observation_response("/item", &object("item", None, b"new")),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
@@ -182,11 +227,11 @@ fn normalized_object_cache_rejects_conflicts_and_evicts_to_its_bound() {
|
||||
object("shared", Some("shared-ref"), b"v1"),
|
||||
);
|
||||
|
||||
let conflict = client.prepare_fetch(&session, "/b/shared").unwrap();
|
||||
let conflict = client.prepare_observe(&session, "/b/shared", 0).unwrap();
|
||||
let mut changed = object("shared", Some("shared-ref"), b"v1");
|
||||
changed.description = Some("contradiction".into());
|
||||
let error = client
|
||||
.complete(conflict.id, fetch_response("/b/shared", &changed))
|
||||
.complete(conflict.id, observation_response("/b/shared", &changed))
|
||||
.unwrap_err();
|
||||
assert_eq!(error.kind, ClientErrorKind::InvalidResponse);
|
||||
|
||||
@@ -220,34 +265,33 @@ fn normalized_aliases_preserve_newer_request_ownership() {
|
||||
"/b/shared",
|
||||
object("shared", Some("shared-ref"), b"initial"),
|
||||
);
|
||||
let request = FetchTreeRequest {
|
||||
let request = ObserveRequest {
|
||||
path: "/a".into(),
|
||||
depth: 1,
|
||||
limit: 4,
|
||||
};
|
||||
let older_tree_response = || FetchTreeResponse {
|
||||
root: ObjectTreeNode {
|
||||
let older_tree_response = || TestObservationResponse {
|
||||
root: TestObservation {
|
||||
object: object("a", None, b"root"),
|
||||
children: vec![ObjectTreeNode {
|
||||
children: vec![TestObservation {
|
||||
object: object("shared", Some("shared-ref"), b"older"),
|
||||
children: vec![],
|
||||
}],
|
||||
},
|
||||
truncated: false,
|
||||
};
|
||||
|
||||
let older_tree = client.prepare_tree(&session, "/a", 1, 4).unwrap();
|
||||
let newer_alias = client.prepare_fetch(&session, "/b/shared").unwrap();
|
||||
let older_tree = client.prepare_observe(&session, "/a", 1).unwrap();
|
||||
let newer_alias = client.prepare_observe(&session, "/b/shared", 0).unwrap();
|
||||
client
|
||||
.complete(
|
||||
newer_alias.id,
|
||||
fetch_response("/b/shared", &object("shared", Some("shared-ref"), b"newer")),
|
||||
observation_response("/b/shared", &object("shared", Some("shared-ref"), b"newer")),
|
||||
)
|
||||
.unwrap();
|
||||
client
|
||||
.complete(
|
||||
older_tree.id,
|
||||
encode_fetch_tree_response(&request, &older_tree_response(), wire_limits()).unwrap(),
|
||||
encode_test_observation_response(&request, &older_tree_response(), wire_limits())
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
@@ -259,12 +303,13 @@ fn normalized_aliases_preserve_newer_request_ownership() {
|
||||
Some(b"newer".to_vec())
|
||||
);
|
||||
|
||||
let older_tree = client.prepare_tree(&session, "/a", 1, 4).unwrap();
|
||||
let newer_alias = client.prepare_fetch(&session, "/b/shared").unwrap();
|
||||
let older_tree = client.prepare_observe(&session, "/a", 1).unwrap();
|
||||
let newer_alias = client.prepare_observe(&session, "/b/shared", 0).unwrap();
|
||||
client
|
||||
.complete(
|
||||
older_tree.id,
|
||||
encode_fetch_tree_response(&request, &older_tree_response(), wire_limits()).unwrap(),
|
||||
encode_test_observation_response(&request, &older_tree_response(), wire_limits())
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(matches!(
|
||||
@@ -278,7 +323,7 @@ fn normalized_aliases_preserve_newer_request_ownership() {
|
||||
client
|
||||
.complete(
|
||||
newer_alias.id,
|
||||
fetch_response(
|
||||
observation_response(
|
||||
"/b/shared",
|
||||
&object("shared", Some("shared-ref"), b"newest"),
|
||||
),
|
||||
@@ -289,12 +334,12 @@ fn normalized_aliases_preserve_newer_request_ownership() {
|
||||
Some(b"newest".to_vec())
|
||||
);
|
||||
|
||||
let older_tree = client.prepare_tree(&session, "/a", 1, 4).unwrap();
|
||||
let newer_alias = client.prepare_fetch(&session, "/b/shared").unwrap();
|
||||
let older_tree = client.prepare_observe(&session, "/a", 1).unwrap();
|
||||
let newer_alias = client.prepare_observe(&session, "/b/shared", 0).unwrap();
|
||||
client
|
||||
.complete(
|
||||
newer_alias.id,
|
||||
fetch_response(
|
||||
observation_response(
|
||||
"/b/shared",
|
||||
&object("shared", Some("shared-ref"), b"newest"),
|
||||
),
|
||||
@@ -305,17 +350,16 @@ fn normalized_aliases_preserve_newer_request_ownership() {
|
||||
let error = client
|
||||
.complete(
|
||||
older_tree.id,
|
||||
encode_fetch_tree_response(
|
||||
encode_test_observation_response(
|
||||
&request,
|
||||
&FetchTreeResponse {
|
||||
root: ObjectTreeNode {
|
||||
&TestObservationResponse {
|
||||
root: TestObservation {
|
||||
object: object("a", None, b"root"),
|
||||
children: vec![ObjectTreeNode {
|
||||
children: vec![TestObservation {
|
||||
object: contradictory,
|
||||
children: vec![],
|
||||
}],
|
||||
},
|
||||
truncated: false,
|
||||
},
|
||||
wire_limits(),
|
||||
)
|
||||
@@ -334,35 +378,33 @@ fn normalized_aliases_preserve_newer_request_ownership() {
|
||||
"/child",
|
||||
object("child", Some("child-ref"), b"stable"),
|
||||
);
|
||||
let older_tree = client.prepare_tree(&session, "/", 1, 4).unwrap();
|
||||
let newer_child = client.prepare_fetch(&session, "/child").unwrap();
|
||||
let older_tree = client.prepare_observe(&session, "/", 1).unwrap();
|
||||
let newer_child = client.prepare_observe(&session, "/child", 0).unwrap();
|
||||
client
|
||||
.complete(
|
||||
newer_child.id,
|
||||
fetch_response("/child", &object("child", Some("child-ref"), b"stable")),
|
||||
observation_response("/child", &object("child", Some("child-ref"), b"stable")),
|
||||
)
|
||||
.unwrap();
|
||||
let mut contradictory = object("child", Some("child-ref"), b"stable");
|
||||
contradictory.description = Some("same-path contradiction".into());
|
||||
let root_request = FetchTreeRequest {
|
||||
let root_request = ObserveRequest {
|
||||
path: "/".into(),
|
||||
depth: 1,
|
||||
limit: 4,
|
||||
};
|
||||
let error = client
|
||||
.complete(
|
||||
older_tree.id,
|
||||
encode_fetch_tree_response(
|
||||
encode_test_observation_response(
|
||||
&root_request,
|
||||
&FetchTreeResponse {
|
||||
root: ObjectTreeNode {
|
||||
&TestObservationResponse {
|
||||
root: TestObservation {
|
||||
object: object("", None, b"root"),
|
||||
children: vec![ObjectTreeNode {
|
||||
children: vec![TestObservation {
|
||||
object: contradictory,
|
||||
children: vec![],
|
||||
}],
|
||||
},
|
||||
truncated: false,
|
||||
},
|
||||
wire_limits(),
|
||||
)
|
||||
@@ -377,61 +419,151 @@ fn normalized_aliases_preserve_newer_request_ownership() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tree_expansion_is_lazy_truncated_and_refreshes_edges() {
|
||||
fn observation_expansion_is_complete_and_refreshes_edges() {
|
||||
let mut client = client(16, 8, 8, 8);
|
||||
let session = client
|
||||
.open_session("https://example.test/wip", SecurityContext::new("subject"))
|
||||
.unwrap();
|
||||
assert!(client.tree(&session, "/").is_none());
|
||||
|
||||
let request = client.prepare_tree(&session, "/", 1, 4).unwrap();
|
||||
let logical_request = FetchTreeRequest {
|
||||
let request = client.prepare_observe(&session, "/", 1).unwrap();
|
||||
let logical_request = ObserveRequest {
|
||||
path: "/".into(),
|
||||
depth: 1,
|
||||
limit: 4,
|
||||
};
|
||||
let tree = FetchTreeResponse {
|
||||
root: ObjectTreeNode {
|
||||
let tree = TestObservationResponse {
|
||||
root: TestObservation {
|
||||
object: object("", None, b"root"),
|
||||
children: vec![ObjectTreeNode {
|
||||
children: vec![TestObservation {
|
||||
object: object("items", None, b"items"),
|
||||
children: vec![],
|
||||
}],
|
||||
},
|
||||
truncated: true,
|
||||
};
|
||||
client
|
||||
.complete(
|
||||
request.id,
|
||||
encode_fetch_tree_response(&logical_request, &tree, wire_limits()).unwrap(),
|
||||
encode_test_observation_response(&logical_request, &tree, wire_limits()).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(client.tree(&session, "/").unwrap().children, ["/items"]);
|
||||
assert!(client.tree(&session, "/").unwrap().truncated);
|
||||
assert!(client.object(&session, "/items").is_some());
|
||||
|
||||
let refresh = client.prepare_tree(&session, "/", 1, 4).unwrap();
|
||||
let refreshed = FetchTreeResponse {
|
||||
root: ObjectTreeNode {
|
||||
let refresh = client.prepare_observe(&session, "/", 1).unwrap();
|
||||
let refreshed = TestObservationResponse {
|
||||
root: TestObservation {
|
||||
object: object("", None, b"root-2"),
|
||||
children: vec![],
|
||||
},
|
||||
truncated: false,
|
||||
};
|
||||
client
|
||||
.complete(
|
||||
refresh.id,
|
||||
encode_fetch_tree_response(&logical_request, &refreshed, wire_limits()).unwrap(),
|
||||
encode_test_observation_response(&logical_request, &refreshed, wire_limits()).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(client.tree(&session, "/").unwrap().children.is_empty());
|
||||
assert!(!client.tree(&session, "/").unwrap().truncated);
|
||||
assert_eq!(
|
||||
client.object(&session, "/items").unwrap().state,
|
||||
ObservationState::Stale
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn depth_zero_observation_preserves_previously_observed_children() {
|
||||
let mut client = client(16, 8, 8, 8);
|
||||
let session = client
|
||||
.open_session("https://example.test/wip", SecurityContext::new("subject"))
|
||||
.unwrap();
|
||||
let deep_request = ObserveRequest {
|
||||
path: "/".into(),
|
||||
depth: 1,
|
||||
};
|
||||
let deep = client.prepare_observe(&session, "/", 1).unwrap();
|
||||
client
|
||||
.complete(
|
||||
deep.id,
|
||||
encode_test_observation_response(
|
||||
&deep_request,
|
||||
&TestObservationResponse {
|
||||
root: TestObservation {
|
||||
object: object("", None, b"root-v1"),
|
||||
children: vec![TestObservation {
|
||||
object: object("child", None, b"child-v1"),
|
||||
children: vec![],
|
||||
}],
|
||||
},
|
||||
},
|
||||
wire_limits(),
|
||||
)
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let shallow = client.prepare_observe(&session, "/", 0).unwrap();
|
||||
client
|
||||
.complete(
|
||||
shallow.id,
|
||||
observation_response("/", &object("", None, b"root-v2")),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(client.tree(&session, "/").unwrap().children, ["/child"]);
|
||||
assert_eq!(
|
||||
client.object(&session, "/child").unwrap().state,
|
||||
ObservationState::Fresh
|
||||
);
|
||||
assert_eq!(
|
||||
client.object(&session, "/").unwrap().validator,
|
||||
Some(b"root-v2".to_vec())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shallow_observation_supersedes_loading_deep_observation_without_leaking_loading_state() {
|
||||
let mut client = client(16, 8, 8, 8);
|
||||
let session = client
|
||||
.open_session("https://example.test/wip", SecurityContext::new("subject"))
|
||||
.unwrap();
|
||||
let deep_request = ObserveRequest {
|
||||
path: "/".into(),
|
||||
depth: 1,
|
||||
};
|
||||
let deep_value = TestObservationResponse {
|
||||
root: TestObservation {
|
||||
object: object("", None, b"old-root"),
|
||||
children: vec![],
|
||||
},
|
||||
};
|
||||
|
||||
let deep = client.prepare_observe(&session, "/", 1).unwrap();
|
||||
let shallow = client.prepare_observe(&session, "/", 0).unwrap();
|
||||
assert_eq!(
|
||||
client.tree(&session, "/").unwrap().state,
|
||||
ObservationState::Stale
|
||||
);
|
||||
assert_eq!(
|
||||
client
|
||||
.complete(
|
||||
deep.id,
|
||||
encode_test_observation_response(&deep_request, &deep_value, wire_limits())
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap(),
|
||||
Completion::StaleResponseRejected(deep.id)
|
||||
);
|
||||
client
|
||||
.complete(
|
||||
shallow.id,
|
||||
observation_response("/", &object("", None, b"new-root")),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
client.tree(&session, "/").unwrap().state,
|
||||
ObservationState::Stale
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_context_fixes_membership_validators_descriptor_and_result_validation() {
|
||||
let mut client = client(8, 8, 8, 8);
|
||||
@@ -686,7 +818,7 @@ fn older_retrieval_cannot_overwrite_success_validator() {
|
||||
},
|
||||
);
|
||||
|
||||
let older_alias = client.prepare_fetch(&session, "/alias/item").unwrap();
|
||||
let older_alias = client.prepare_observe(&session, "/alias/item", 0).unwrap();
|
||||
let call = client
|
||||
.prepare_call(&session, "/item", "math", "calculate", args(7))
|
||||
.unwrap();
|
||||
@@ -723,7 +855,7 @@ fn older_retrieval_cannot_overwrite_success_validator() {
|
||||
client
|
||||
.complete(
|
||||
older_alias.id,
|
||||
fetch_response(
|
||||
observation_response(
|
||||
"/alias/item",
|
||||
&object("item", Some("shared-ref"), b"object-v1"),
|
||||
),
|
||||
@@ -746,7 +878,7 @@ fn older_retrieval_cannot_overwrite_success_validator() {
|
||||
"/item",
|
||||
object("item", Some("shared-ref"), b"object-v2"),
|
||||
);
|
||||
let older_alias = client.prepare_fetch(&session, "/second/item").unwrap();
|
||||
let older_alias = client.prepare_observe(&session, "/second/item", 0).unwrap();
|
||||
let call = client
|
||||
.prepare_call(&session, "/item", "math", "calculate", args(8))
|
||||
.unwrap();
|
||||
@@ -769,7 +901,7 @@ fn older_retrieval_cannot_overwrite_success_validator() {
|
||||
client
|
||||
.complete(
|
||||
older_alias.id,
|
||||
fetch_response(
|
||||
observation_response(
|
||||
"/second/item",
|
||||
&object("item", Some("shared-ref"), b"object-v2"),
|
||||
),
|
||||
@@ -808,7 +940,10 @@ fn in_flight_calls_pin_target_identity_until_staleness_is_applied() {
|
||||
client.mark_dispatched(call.id).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
client.prepare_fetch(&session, "/other").unwrap_err().kind,
|
||||
client
|
||||
.prepare_observe(&session, "/other", 0)
|
||||
.unwrap_err()
|
||||
.kind,
|
||||
ClientErrorKind::Capacity
|
||||
);
|
||||
let unknown = ProtocolError {
|
||||
@@ -980,57 +1115,55 @@ fn overlapping_tree_work_preserves_newer_subject_request_ownership() {
|
||||
let session = client
|
||||
.open_session("https://example.test/wip", SecurityContext::new("subject"))
|
||||
.unwrap();
|
||||
let tree_request = FetchTreeRequest {
|
||||
let tree_request = ObserveRequest {
|
||||
path: "/".into(),
|
||||
depth: 1,
|
||||
limit: 4,
|
||||
};
|
||||
let tree_value = FetchTreeResponse {
|
||||
root: ObjectTreeNode {
|
||||
let tree_value = TestObservationResponse {
|
||||
root: TestObservation {
|
||||
object: object("", None, b"root"),
|
||||
children: vec![ObjectTreeNode {
|
||||
children: vec![TestObservation {
|
||||
object: object("child", None, b"tree-child"),
|
||||
children: vec![],
|
||||
}],
|
||||
},
|
||||
truncated: false,
|
||||
};
|
||||
|
||||
let old_tree = client.prepare_tree(&session, "/", 1, 4).unwrap();
|
||||
let newer_fetch = client.prepare_fetch(&session, "/child").unwrap();
|
||||
let old_tree = client.prepare_observe(&session, "/", 1).unwrap();
|
||||
let newer_observation = client.prepare_observe(&session, "/child", 0).unwrap();
|
||||
client
|
||||
.complete(
|
||||
old_tree.id,
|
||||
encode_fetch_tree_response(&tree_request, &tree_value, wire_limits()).unwrap(),
|
||||
encode_test_observation_response(&tree_request, &tree_value, wire_limits()).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(matches!(
|
||||
client.object(&session, "/child").unwrap().state,
|
||||
ObservationState::Loading(id) if id == newer_fetch.id
|
||||
ObservationState::Loading(id) if id == newer_observation.id
|
||||
));
|
||||
client
|
||||
.complete(
|
||||
newer_fetch.id,
|
||||
fetch_response("/child", &object("child", None, b"newer-fetch")),
|
||||
newer_observation.id,
|
||||
observation_response("/child", &object("child", None, b"newer-observation")),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
client.object(&session, "/child").unwrap().validator,
|
||||
Some(b"newer-fetch".to_vec())
|
||||
Some(b"newer-observation".to_vec())
|
||||
);
|
||||
|
||||
let older_tree = client.prepare_tree(&session, "/", 1, 4).unwrap();
|
||||
let completed_newer_fetch = client.prepare_fetch(&session, "/child").unwrap();
|
||||
let older_tree = client.prepare_observe(&session, "/", 1).unwrap();
|
||||
let completed_newer_observation = client.prepare_observe(&session, "/child", 0).unwrap();
|
||||
client
|
||||
.complete(
|
||||
completed_newer_fetch.id,
|
||||
fetch_response("/child", &object("child", None, b"completed-newer")),
|
||||
completed_newer_observation.id,
|
||||
observation_response("/child", &object("child", None, b"completed-newer")),
|
||||
)
|
||||
.unwrap();
|
||||
client
|
||||
.complete(
|
||||
older_tree.id,
|
||||
encode_fetch_tree_response(&tree_request, &tree_value, wire_limits()).unwrap(),
|
||||
encode_test_observation_response(&tree_request, &tree_value, wire_limits()).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
@@ -1038,25 +1171,24 @@ fn overlapping_tree_work_preserves_newer_subject_request_ownership() {
|
||||
Some(b"completed-newer".to_vec())
|
||||
);
|
||||
|
||||
let older_omitting_tree = client.prepare_tree(&session, "/", 1, 4).unwrap();
|
||||
let newer_fetch = client.prepare_fetch(&session, "/child").unwrap();
|
||||
let older_omitting_tree = client.prepare_observe(&session, "/", 1).unwrap();
|
||||
let newer_observation = client.prepare_observe(&session, "/child", 0).unwrap();
|
||||
client
|
||||
.complete(
|
||||
newer_fetch.id,
|
||||
fetch_response("/child", &object("child", None, b"newer-than-omission")),
|
||||
newer_observation.id,
|
||||
observation_response("/child", &object("child", None, b"newer-than-omission")),
|
||||
)
|
||||
.unwrap();
|
||||
client
|
||||
.complete(
|
||||
older_omitting_tree.id,
|
||||
encode_fetch_tree_response(
|
||||
encode_test_observation_response(
|
||||
&tree_request,
|
||||
&FetchTreeResponse {
|
||||
root: ObjectTreeNode {
|
||||
&TestObservationResponse {
|
||||
root: TestObservation {
|
||||
object: object("", None, b"older-root"),
|
||||
children: vec![],
|
||||
},
|
||||
truncated: false,
|
||||
},
|
||||
wire_limits(),
|
||||
)
|
||||
@@ -1067,12 +1199,12 @@ fn overlapping_tree_work_preserves_newer_subject_request_ownership() {
|
||||
assert_eq!(child.state, ObservationState::Fresh);
|
||||
assert_eq!(child.validator, Some(b"newer-than-omission".to_vec()));
|
||||
|
||||
let old_tree = client.prepare_tree(&session, "/", 1, 4).unwrap();
|
||||
let newer_child_tree = client.prepare_tree(&session, "/child", 0, 1).unwrap();
|
||||
let old_tree = client.prepare_observe(&session, "/", 1).unwrap();
|
||||
let newer_child_tree = client.prepare_observe(&session, "/child", 1).unwrap();
|
||||
client
|
||||
.complete(
|
||||
old_tree.id,
|
||||
encode_fetch_tree_response(&tree_request, &tree_value, wire_limits()).unwrap(),
|
||||
encode_test_observation_response(&tree_request, &tree_value, wire_limits()).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(matches!(
|
||||
@@ -1088,22 +1220,20 @@ fn overlapping_tree_work_preserves_newer_subject_request_ownership() {
|
||||
child_observation.validator,
|
||||
Some(b"newer-than-omission".to_vec())
|
||||
);
|
||||
let child_request = FetchTreeRequest {
|
||||
let child_request = ObserveRequest {
|
||||
path: "/child".into(),
|
||||
depth: 0,
|
||||
limit: 1,
|
||||
depth: 1,
|
||||
};
|
||||
let child_value = FetchTreeResponse {
|
||||
root: ObjectTreeNode {
|
||||
let child_value = TestObservationResponse {
|
||||
root: TestObservation {
|
||||
object: object("child", None, b"newer-tree"),
|
||||
children: vec![],
|
||||
},
|
||||
truncated: false,
|
||||
};
|
||||
client
|
||||
.complete(
|
||||
newer_child_tree.id,
|
||||
encode_fetch_tree_response(&child_request, &child_value, wire_limits()).unwrap(),
|
||||
encode_test_observation_response(&child_request, &child_value, wire_limits()).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
@@ -1111,19 +1241,18 @@ fn overlapping_tree_work_preserves_newer_subject_request_ownership() {
|
||||
ObservationState::Fresh
|
||||
);
|
||||
|
||||
let older_parent = client.prepare_tree(&session, "/", 1, 4).unwrap();
|
||||
let newer_child = client.prepare_tree(&session, "/child", 0, 1).unwrap();
|
||||
let older_parent = client.prepare_observe(&session, "/", 1).unwrap();
|
||||
let newer_child = client.prepare_observe(&session, "/child", 1).unwrap();
|
||||
client
|
||||
.complete(
|
||||
newer_child.id,
|
||||
encode_fetch_tree_response(
|
||||
encode_test_observation_response(
|
||||
&child_request,
|
||||
&FetchTreeResponse {
|
||||
root: ObjectTreeNode {
|
||||
&TestObservationResponse {
|
||||
root: TestObservation {
|
||||
object: object("child", None, b"completed-newer-tree"),
|
||||
children: vec![],
|
||||
},
|
||||
truncated: false,
|
||||
},
|
||||
wire_limits(),
|
||||
)
|
||||
@@ -1133,7 +1262,7 @@ fn overlapping_tree_work_preserves_newer_subject_request_ownership() {
|
||||
client
|
||||
.complete(
|
||||
older_parent.id,
|
||||
encode_fetch_tree_response(&tree_request, &tree_value, wire_limits()).unwrap(),
|
||||
encode_test_observation_response(&tree_request, &tree_value, wire_limits()).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
@@ -1152,8 +1281,8 @@ fn superseded_transport_failure_does_not_poison_newer_refresh() {
|
||||
let session = client
|
||||
.open_session("https://example.test/wip", SecurityContext::new("subject"))
|
||||
.unwrap();
|
||||
let old = client.prepare_fetch(&session, "/item").unwrap();
|
||||
let newer = client.prepare_fetch(&session, "/item").unwrap();
|
||||
let old = client.prepare_observe(&session, "/item", 0).unwrap();
|
||||
let newer = client.prepare_observe(&session, "/item", 0).unwrap();
|
||||
assert_eq!(
|
||||
client
|
||||
.fail_transport(old.id, "old connection failed")
|
||||
@@ -1167,12 +1296,12 @@ fn superseded_transport_failure_does_not_poison_newer_refresh() {
|
||||
client
|
||||
.complete(
|
||||
newer.id,
|
||||
fetch_response("/item", &object("item", None, b"new")),
|
||||
observation_response("/item", &object("item", None, b"new")),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let old = client.prepare_tree(&session, "/", 0, 1).unwrap();
|
||||
let newer_tree = client.prepare_tree(&session, "/", 0, 1).unwrap();
|
||||
let old = client.prepare_observe(&session, "/", 1).unwrap();
|
||||
let newer_tree = client.prepare_observe(&session, "/", 1).unwrap();
|
||||
assert_eq!(
|
||||
client
|
||||
.fail_transport(old.id, "old tree connection failed")
|
||||
@@ -1186,18 +1315,16 @@ fn superseded_transport_failure_does_not_poison_newer_refresh() {
|
||||
client
|
||||
.complete(
|
||||
newer_tree.id,
|
||||
encode_fetch_tree_response(
|
||||
&FetchTreeRequest {
|
||||
encode_test_observation_response(
|
||||
&ObserveRequest {
|
||||
path: "/".into(),
|
||||
depth: 0,
|
||||
limit: 1,
|
||||
depth: 1,
|
||||
},
|
||||
&FetchTreeResponse {
|
||||
root: ObjectTreeNode {
|
||||
&TestObservationResponse {
|
||||
root: TestObservation {
|
||||
object: object("", None, b"root"),
|
||||
children: vec![],
|
||||
},
|
||||
truncated: false,
|
||||
},
|
||||
wire_limits(),
|
||||
)
|
||||
@@ -1283,9 +1410,12 @@ fn entry_eviction_never_discards_a_loading_tree_subject() {
|
||||
.open_session("https://example.test/wip", SecurityContext::new("subject"))
|
||||
.unwrap();
|
||||
observe_object(&mut client, &session, "/", object("", None, b"root"));
|
||||
let tree = client.prepare_tree(&session, "/", 0, 1).unwrap();
|
||||
let tree = client.prepare_observe(&session, "/", 1).unwrap();
|
||||
assert_eq!(
|
||||
client.prepare_fetch(&session, "/other").unwrap_err().kind,
|
||||
client
|
||||
.prepare_observe(&session, "/other", 0)
|
||||
.unwrap_err()
|
||||
.kind,
|
||||
ClientErrorKind::Capacity
|
||||
);
|
||||
assert!(matches!(
|
||||
@@ -1295,18 +1425,16 @@ fn entry_eviction_never_discards_a_loading_tree_subject() {
|
||||
client
|
||||
.complete(
|
||||
tree.id,
|
||||
encode_fetch_tree_response(
|
||||
&FetchTreeRequest {
|
||||
encode_test_observation_response(
|
||||
&ObserveRequest {
|
||||
path: "/".into(),
|
||||
depth: 0,
|
||||
limit: 1,
|
||||
depth: 1,
|
||||
},
|
||||
&FetchTreeResponse {
|
||||
root: ObjectTreeNode {
|
||||
&TestObservationResponse {
|
||||
root: TestObservation {
|
||||
object: object("", None, b"tree"),
|
||||
children: vec![],
|
||||
},
|
||||
truncated: false,
|
||||
},
|
||||
wire_limits(),
|
||||
)
|
||||
@@ -1326,7 +1454,7 @@ fn failed_entry_and_tree_observation_metadata_is_bounded() {
|
||||
.open_session("https://example.test/wip", SecurityContext::new("subject"))
|
||||
.unwrap();
|
||||
for path in ["/one", "/two", "/three"] {
|
||||
let request = client.prepare_fetch(&session, path).unwrap();
|
||||
let request = client.prepare_observe(&session, path, 0).unwrap();
|
||||
assert_eq!(
|
||||
client
|
||||
.fail_transport(request.id, "retrieval failed")
|
||||
@@ -1340,7 +1468,7 @@ fn failed_entry_and_tree_observation_metadata_is_bounded() {
|
||||
assert!(client.object(&session, "/three").is_some());
|
||||
|
||||
for path in ["/alpha", "/beta", "/gamma"] {
|
||||
let request = client.prepare_tree(&session, path, 0, 1).unwrap();
|
||||
let request = client.prepare_observe(&session, path, 1).unwrap();
|
||||
assert_eq!(
|
||||
client
|
||||
.fail_transport(request.id, "tree failed")
|
||||
@@ -1359,16 +1487,19 @@ fn in_flight_and_history_are_bounded() {
|
||||
let session = client
|
||||
.open_session("https://example.test/wip", SecurityContext::new("subject"))
|
||||
.unwrap();
|
||||
let first = client.prepare_fetch(&session, "/one").unwrap();
|
||||
let error = client.prepare_fetch(&session, "/two").unwrap_err();
|
||||
let first = client.prepare_observe(&session, "/one", 0).unwrap();
|
||||
let error = client.prepare_observe(&session, "/two", 0).unwrap_err();
|
||||
assert_eq!(error.kind, ClientErrorKind::Capacity);
|
||||
client
|
||||
.complete(first.id, fetch_response("/one", &object("one", None, b"1")))
|
||||
.complete(
|
||||
first.id,
|
||||
observation_response("/one", &object("one", None, b"1")),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Diagnostic history evicts oldest entries at the configured bound.
|
||||
let stale_old = client.prepare_fetch(&session, "/one").unwrap();
|
||||
let stale_new = client.prepare_fetch(&session, "/one").unwrap_err();
|
||||
let stale_old = client.prepare_observe(&session, "/one", 0).unwrap();
|
||||
let stale_new = client.prepare_observe(&session, "/one", 0).unwrap_err();
|
||||
assert_eq!(stale_new.kind, ClientErrorKind::Capacity);
|
||||
client
|
||||
.complete(
|
||||
|
||||
Reference in New Issue
Block a user