fix: keep object observations fresh after successful calls
This commit is contained in:
@@ -1261,11 +1261,14 @@ impl Client {
|
|||||||
let session_id = context.session.clone();
|
let session_id = context.session.clone();
|
||||||
let maximum = self.limits.history;
|
let maximum = self.limits.history;
|
||||||
let state = self.session_mut(&session_id)?;
|
let state = self.session_mut(&session_id)?;
|
||||||
if let Some(object) = state.objects.get_mut(&context.object_key) {
|
if let Some(object) = state.objects.get_mut(&context.object_key)
|
||||||
object.object.validator = value.validator.clone();
|
&& object.revision <= id
|
||||||
object.revision = object.revision.max(id);
|
{
|
||||||
|
if let Some(validator) = value.validator.clone() {
|
||||||
|
object.object.validator = Some(validator);
|
||||||
|
}
|
||||||
|
object.revision = id;
|
||||||
}
|
}
|
||||||
state.mark_object_stale(context.object_key, id);
|
|
||||||
let record = CallRecord {
|
let record = CallRecord {
|
||||||
request_id: id,
|
request_id: id,
|
||||||
context,
|
context,
|
||||||
|
|||||||
@@ -525,14 +525,147 @@ fn call_context_fixes_membership_validators_descriptor_and_result_validation() {
|
|||||||
client.interface(&session, "math").unwrap().descriptor,
|
client.interface(&session, "math").unwrap().descriptor,
|
||||||
Some(descriptor(TypeExpr::String))
|
Some(descriptor(TypeExpr::String))
|
||||||
);
|
);
|
||||||
|
let observation = client.object(&session, "/item").unwrap();
|
||||||
|
assert_eq!(observation.state, ObservationState::Fresh);
|
||||||
|
assert_eq!(observation.validator, Some(b"object-v2".to_vec()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn successful_call_without_validator_preserves_observed_validator() {
|
||||||
|
let mut client = client(8, 8, 8, 8);
|
||||||
|
let session = client
|
||||||
|
.open_session("https://example.test/wip", SecurityContext::new("subject"))
|
||||||
|
.unwrap();
|
||||||
|
observe_object(
|
||||||
|
&mut client,
|
||||||
|
&session,
|
||||||
|
"/item",
|
||||||
|
object("item", None, b"object-v1"),
|
||||||
|
);
|
||||||
|
observe_interface(
|
||||||
|
&mut client,
|
||||||
|
&session,
|
||||||
|
FetchInterfaceResponse {
|
||||||
|
interface: "math".into(),
|
||||||
|
descriptor: descriptor(TypeExpr::Integer),
|
||||||
|
validator: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let call = client
|
||||||
|
.prepare_call(&session, "/item", "math", "calculate", args(1))
|
||||||
|
.unwrap();
|
||||||
|
let request = client
|
||||||
|
.pending_call_context(call.id)
|
||||||
|
.unwrap()
|
||||||
|
.request()
|
||||||
|
.clone();
|
||||||
|
let fixed_descriptor = client
|
||||||
|
.pending_call_context(call.id)
|
||||||
|
.unwrap()
|
||||||
|
.descriptor()
|
||||||
|
.clone();
|
||||||
|
client.mark_dispatched(call.id).unwrap();
|
||||||
|
client
|
||||||
|
.complete(
|
||||||
|
call.id,
|
||||||
|
encode_call_operation_response(
|
||||||
|
&request,
|
||||||
|
&fixed_descriptor,
|
||||||
|
&CallOperationResponse {
|
||||||
|
result: Value::Integer(2),
|
||||||
|
validator: None,
|
||||||
|
},
|
||||||
|
wire_limits(),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let observation = client.object(&session, "/item").unwrap();
|
||||||
|
assert_eq!(observation.state, ObservationState::Fresh);
|
||||||
|
assert_eq!(observation.validator, Some(b"object-v1".to_vec()));
|
||||||
|
|
||||||
|
let next = client
|
||||||
|
.prepare_call(&session, "/item", "math", "calculate", args(2))
|
||||||
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client.object(&session, "/item").unwrap().state,
|
client
|
||||||
ObservationState::Stale
|
.pending_call_context(next.id)
|
||||||
|
.unwrap()
|
||||||
|
.request()
|
||||||
|
.target
|
||||||
|
.validator,
|
||||||
|
Some(b"object-v1".to_vec())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn older_retrieval_cannot_restore_freshness_after_operation_completion() {
|
fn newer_observation_prevents_older_success_validator_overwrite() {
|
||||||
|
let mut client = client(8, 8, 8, 8);
|
||||||
|
let session = client
|
||||||
|
.open_session("https://example.test/wip", SecurityContext::new("subject"))
|
||||||
|
.unwrap();
|
||||||
|
observe_object(
|
||||||
|
&mut client,
|
||||||
|
&session,
|
||||||
|
"/item",
|
||||||
|
object("item", None, b"object-v1"),
|
||||||
|
);
|
||||||
|
observe_interface(
|
||||||
|
&mut client,
|
||||||
|
&session,
|
||||||
|
FetchInterfaceResponse {
|
||||||
|
interface: "math".into(),
|
||||||
|
descriptor: descriptor(TypeExpr::Integer),
|
||||||
|
validator: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let call = client
|
||||||
|
.prepare_call(&session, "/item", "math", "calculate", args(1))
|
||||||
|
.unwrap();
|
||||||
|
let request = client
|
||||||
|
.pending_call_context(call.id)
|
||||||
|
.unwrap()
|
||||||
|
.request()
|
||||||
|
.clone();
|
||||||
|
let fixed_descriptor = client
|
||||||
|
.pending_call_context(call.id)
|
||||||
|
.unwrap()
|
||||||
|
.descriptor()
|
||||||
|
.clone();
|
||||||
|
client.mark_dispatched(call.id).unwrap();
|
||||||
|
|
||||||
|
observe_object(
|
||||||
|
&mut client,
|
||||||
|
&session,
|
||||||
|
"/item",
|
||||||
|
object("item", None, b"object-v3"),
|
||||||
|
);
|
||||||
|
client
|
||||||
|
.complete(
|
||||||
|
call.id,
|
||||||
|
encode_call_operation_response(
|
||||||
|
&request,
|
||||||
|
&fixed_descriptor,
|
||||||
|
&CallOperationResponse {
|
||||||
|
result: Value::Integer(2),
|
||||||
|
validator: Some(b"object-v2".to_vec()),
|
||||||
|
},
|
||||||
|
wire_limits(),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let observation = client.object(&session, "/item").unwrap();
|
||||||
|
assert_eq!(observation.state, ObservationState::Fresh);
|
||||||
|
assert_eq!(observation.validator, Some(b"object-v3".to_vec()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn older_retrieval_cannot_overwrite_success_validator() {
|
||||||
let mut client = client(8, 8, 8, 8);
|
let mut client = client(8, 8, 8, 8);
|
||||||
let session = client
|
let session = client
|
||||||
.open_session("https://example.test/wip", SecurityContext::new("subject"))
|
.open_session("https://example.test/wip", SecurityContext::new("subject"))
|
||||||
@@ -583,10 +716,9 @@ fn older_retrieval_cannot_restore_freshness_after_operation_completion() {
|
|||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
let current = client.object(&session, "/item").unwrap();
|
||||||
client.object(&session, "/item").unwrap().state,
|
assert_eq!(current.state, ObservationState::Fresh);
|
||||||
ObservationState::Stale
|
assert_eq!(current.validator, Some(b"object-v2".to_vec()));
|
||||||
);
|
|
||||||
|
|
||||||
client
|
client
|
||||||
.complete(
|
.complete(
|
||||||
|
|||||||
@@ -121,8 +121,8 @@ layout.
|
|||||||
## Validator and failure behavior
|
## Validator and failure behavior
|
||||||
|
|
||||||
Object and interface validators are observed and attached automatically by
|
Object and interface validators are observed and attached automatically by
|
||||||
`wip-client`; users neither copy nor edit them. A successful operation conservatively
|
`wip-client`; users neither copy nor edit them. A successful operation may update the
|
||||||
marks related object observations stale because metadata may have changed.
|
cached Object validator but does not by itself make the Object observation stale.
|
||||||
Validator mismatch, interface mismatch, `OperationOutcomeUnknown`, undecodable
|
Validator mismatch, interface mismatch, `OperationOutcomeUnknown`, undecodable
|
||||||
post-dispatch responses, and post-dispatch timeout/disconnect are displayed using
|
post-dispatch responses, and post-dispatch timeout/disconnect are displayed using
|
||||||
the runtime's authoritative transition.
|
the runtime's authoritative transition.
|
||||||
|
|||||||
@@ -91,11 +91,7 @@ fn real_http_filesystem_workflow_is_lazy_validator_safe_and_symlink_confined() {
|
|||||||
.contains("68656c6c6f20776f726c64")
|
.contains("68656c6c6f20776f726c64")
|
||||||
);
|
);
|
||||||
|
|
||||||
// Calls conservatively stale the object even for a read operation; refresh
|
assert_eq!(runtime.app().entries["/hello.txt"].state, ViewState::Fresh);
|
||||||
// before constructing the separately confirmed write call.
|
|
||||||
runtime.app_mut().focus = Focus::Tree;
|
|
||||||
runtime.handle_key(InputKey::Refresh);
|
|
||||||
settle(&mut runtime);
|
|
||||||
runtime.app_mut().focus = Focus::Operations;
|
runtime.app_mut().focus = Focus::Operations;
|
||||||
|
|
||||||
// Load the separately-qualified write interface and replace with the
|
// Load the separately-qualified write interface and replace with the
|
||||||
@@ -112,15 +108,11 @@ fn real_http_filesystem_workflow_is_lazy_validator_safe_and_symlink_confined() {
|
|||||||
std::fs::read(temporary.path().join("hello.txt")).expect("updated file"),
|
std::fs::read(temporary.path().join("hello.txt")).expect("updated file"),
|
||||||
b"updated"
|
b"updated"
|
||||||
);
|
);
|
||||||
assert_eq!(runtime.app().entries["/hello.txt"].state, ViewState::Stale);
|
|
||||||
|
|
||||||
// Reobserve, prepare a replacement from that observation, then mutate the
|
|
||||||
// file externally before explicit execution. The stale validator is rejected,
|
|
||||||
// the Entry becomes stale, and no automatic call is created.
|
|
||||||
runtime.app_mut().focus = Focus::Tree;
|
|
||||||
runtime.handle_key(InputKey::Refresh);
|
|
||||||
settle(&mut runtime);
|
|
||||||
assert_eq!(runtime.app().entries["/hello.txt"].state, ViewState::Fresh);
|
assert_eq!(runtime.app().entries["/hello.txt"].state, ViewState::Fresh);
|
||||||
|
|
||||||
|
// Prepare another replacement using the post-operation validator, then mutate
|
||||||
|
// the file externally before explicit execution. The stale validator is rejected,
|
||||||
|
// the Entry becomes stale, and no automatic call is created.
|
||||||
runtime.app_mut().focus = Focus::Operations;
|
runtime.app_mut().focus = Focus::Operations;
|
||||||
runtime.app_mut().operation_cursor = 3;
|
runtime.app_mut().operation_cursor = 3;
|
||||||
runtime.handle_key(InputKey::Enter);
|
runtime.handle_key(InputKey::Enter);
|
||||||
|
|||||||
Reference in New Issue
Block a user