fix(client): preserve observation freshness
This commit is contained in:
@@ -252,6 +252,10 @@ fn tree_expansion_is_lazy_truncated_and_refreshes_edges() {
|
||||
.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]
|
||||
@@ -486,6 +490,252 @@ fn protocol_and_transport_unknown_outcomes_stale_without_retry() {
|
||||
assert_eq!(client.call_history(&session).unwrap().len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overlapping_tree_work_preserves_newer_subject_request_ownership() {
|
||||
let mut client = client(8, 8, 8, 8);
|
||||
let session = client
|
||||
.open_session("https://example.test/wip", SecurityContext::new("subject"))
|
||||
.unwrap();
|
||||
let tree_request = FetchTreeRequest {
|
||||
path: "/".into(),
|
||||
depth: 1,
|
||||
limit: 4,
|
||||
};
|
||||
let tree_value = FetchTreeResponse {
|
||||
root: ObjectTreeNode {
|
||||
object: object("", None, b"root"),
|
||||
children: vec![ObjectTreeNode {
|
||||
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();
|
||||
client
|
||||
.complete(
|
||||
old_tree.id,
|
||||
encode_fetch_tree_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
|
||||
));
|
||||
client
|
||||
.complete(
|
||||
newer_fetch.id,
|
||||
fetch_response("/child", &object("child", None, b"newer-fetch")),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
client.object(&session, "/child").unwrap().validator,
|
||||
Some(b"newer-fetch".to_vec())
|
||||
);
|
||||
|
||||
let old_tree = client.prepare_tree(&session, "/", 1, 4).unwrap();
|
||||
let newer_child_tree = client.prepare_tree(&session, "/child", 0, 1).unwrap();
|
||||
client
|
||||
.complete(
|
||||
old_tree.id,
|
||||
encode_fetch_tree_response(&tree_request, &tree_value, wire_limits()).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(matches!(
|
||||
client.tree(&session, "/child").unwrap().state,
|
||||
ObservationState::Loading(id) if id == newer_child_tree.id
|
||||
));
|
||||
let child_request = FetchTreeRequest {
|
||||
path: "/child".into(),
|
||||
depth: 0,
|
||||
limit: 1,
|
||||
};
|
||||
let child_value = FetchTreeResponse {
|
||||
root: ObjectTreeNode {
|
||||
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(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
client.tree(&session, "/child").unwrap().state,
|
||||
ObservationState::Fresh
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn superseded_transport_failure_does_not_poison_newer_refresh() {
|
||||
let mut client = client(8, 8, 8, 8);
|
||||
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();
|
||||
assert_eq!(
|
||||
client
|
||||
.fail_transport(old.id, "old connection failed")
|
||||
.unwrap(),
|
||||
Completion::StaleResponseRejected(old.id)
|
||||
);
|
||||
assert!(matches!(
|
||||
client.object(&session, "/item").unwrap().state,
|
||||
ObservationState::Loading(id) if id == newer.id
|
||||
));
|
||||
client
|
||||
.complete(
|
||||
newer.id,
|
||||
fetch_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();
|
||||
assert_eq!(
|
||||
client
|
||||
.fail_transport(old.id, "old tree connection failed")
|
||||
.unwrap(),
|
||||
Completion::StaleResponseRejected(old.id)
|
||||
);
|
||||
assert!(matches!(
|
||||
client.tree(&session, "/").unwrap().state,
|
||||
ObservationState::Loading(id) if id == newer_tree.id
|
||||
));
|
||||
client
|
||||
.complete(
|
||||
newer_tree.id,
|
||||
encode_fetch_tree_response(
|
||||
&FetchTreeRequest {
|
||||
path: "/".into(),
|
||||
depth: 0,
|
||||
limit: 1,
|
||||
},
|
||||
&FetchTreeResponse {
|
||||
root: ObjectTreeNode {
|
||||
object: object("", None, b"root"),
|
||||
children: vec![],
|
||||
},
|
||||
truncated: false,
|
||||
},
|
||||
wire_limits(),
|
||||
)
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let old = client.prepare_interface(&session, "math").unwrap();
|
||||
let newer = client.prepare_interface(&session, "math").unwrap();
|
||||
assert_eq!(
|
||||
client
|
||||
.fail_transport(old.id, "old connection failed")
|
||||
.unwrap(),
|
||||
Completion::StaleResponseRejected(old.id)
|
||||
);
|
||||
assert!(matches!(
|
||||
client.interface(&session, "math").unwrap().state,
|
||||
ObservationState::Loading(id) if id == newer.id
|
||||
));
|
||||
let value = FetchInterfaceResponse {
|
||||
interface: "math".into(),
|
||||
descriptor: descriptor(TypeExpr::Integer),
|
||||
validator: Some(b"current".to_vec()),
|
||||
};
|
||||
client
|
||||
.complete(
|
||||
newer.id,
|
||||
encode_fetch_interface_response(
|
||||
&FetchInterfaceRequest {
|
||||
interface: "math".into(),
|
||||
},
|
||||
&value,
|
||||
wire_limits(),
|
||||
)
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validator_free_interface_observation_remains_immutable() {
|
||||
let mut client = client(8, 8, 8, 8);
|
||||
let session = client
|
||||
.open_session("https://example.test/wip", SecurityContext::new("subject"))
|
||||
.unwrap();
|
||||
observe_interface(
|
||||
&mut client,
|
||||
&session,
|
||||
FetchInterfaceResponse {
|
||||
interface: "math".into(),
|
||||
descriptor: descriptor(TypeExpr::Integer),
|
||||
validator: None,
|
||||
},
|
||||
);
|
||||
|
||||
let refresh = client.prepare_interface(&session, "math").unwrap();
|
||||
let contradictory = FetchInterfaceResponse {
|
||||
interface: "math".into(),
|
||||
descriptor: descriptor(TypeExpr::Integer),
|
||||
validator: Some(b"became-mutable".to_vec()),
|
||||
};
|
||||
let error = client
|
||||
.complete(
|
||||
refresh.id,
|
||||
encode_fetch_interface_response(
|
||||
&FetchInterfaceRequest {
|
||||
interface: "math".into(),
|
||||
},
|
||||
&contradictory,
|
||||
wire_limits(),
|
||||
)
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(error.kind, ClientErrorKind::InvalidResponse);
|
||||
assert_eq!(client.interface(&session, "math").unwrap().validator, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failed_entry_and_tree_observation_metadata_is_bounded() {
|
||||
let mut client = client(2, 8, 2, 8);
|
||||
let session = client
|
||||
.open_session("https://example.test/wip", SecurityContext::new("subject"))
|
||||
.unwrap();
|
||||
for path in ["/one", "/two", "/three"] {
|
||||
let request = client.prepare_fetch(&session, path).unwrap();
|
||||
assert_eq!(
|
||||
client
|
||||
.fail_transport(request.id, "retrieval failed")
|
||||
.unwrap_err()
|
||||
.kind,
|
||||
ClientErrorKind::Transport
|
||||
);
|
||||
}
|
||||
assert!(client.object(&session, "/one").is_none());
|
||||
assert!(client.object(&session, "/two").is_some());
|
||||
assert!(client.object(&session, "/three").is_some());
|
||||
|
||||
for path in ["/alpha", "/beta", "/gamma"] {
|
||||
let request = client.prepare_tree(&session, path, 0, 1).unwrap();
|
||||
assert_eq!(
|
||||
client
|
||||
.fail_transport(request.id, "tree failed")
|
||||
.unwrap_err()
|
||||
.kind,
|
||||
ClientErrorKind::Transport
|
||||
);
|
||||
}
|
||||
assert!(client.tree(&session, "/alpha").is_none());
|
||||
assert!(client.tree(&session, "/beta").is_some());
|
||||
assert!(client.tree(&session, "/gamma").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn in_flight_and_history_are_bounded() {
|
||||
let mut client = client(8, 8, 1, 1);
|
||||
|
||||
Reference in New Issue
Block a user