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 maximum = self.limits.history;
|
||||
let state = self.session_mut(&session_id)?;
|
||||
if let Some(object) = state.objects.get_mut(&context.object_key) {
|
||||
object.object.validator = value.validator.clone();
|
||||
object.revision = object.revision.max(id);
|
||||
if let Some(object) = state.objects.get_mut(&context.object_key)
|
||||
&& object.revision <= 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 {
|
||||
request_id: id,
|
||||
context,
|
||||
|
||||
@@ -525,14 +525,147 @@ fn call_context_fixes_membership_validators_descriptor_and_result_validation() {
|
||||
client.interface(&session, "math").unwrap().descriptor,
|
||||
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!(
|
||||
client.object(&session, "/item").unwrap().state,
|
||||
ObservationState::Stale
|
||||
client
|
||||
.pending_call_context(next.id)
|
||||
.unwrap()
|
||||
.request()
|
||||
.target
|
||||
.validator,
|
||||
Some(b"object-v1".to_vec())
|
||||
);
|
||||
}
|
||||
|
||||
#[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 session = client
|
||||
.open_session("https://example.test/wip", SecurityContext::new("subject"))
|
||||
@@ -583,10 +716,9 @@ fn older_retrieval_cannot_restore_freshness_after_operation_completion() {
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
client.object(&session, "/item").unwrap().state,
|
||||
ObservationState::Stale
|
||||
);
|
||||
let current = client.object(&session, "/item").unwrap();
|
||||
assert_eq!(current.state, ObservationState::Fresh);
|
||||
assert_eq!(current.validator, Some(b"object-v2".to_vec()));
|
||||
|
||||
client
|
||||
.complete(
|
||||
|
||||
@@ -121,8 +121,8 @@ layout.
|
||||
## Validator and failure behavior
|
||||
|
||||
Object and interface validators are observed and attached automatically by
|
||||
`wip-client`; users neither copy nor edit them. A successful operation conservatively
|
||||
marks related object observations stale because metadata may have changed.
|
||||
`wip-client`; users neither copy nor edit them. A successful operation may update the
|
||||
cached Object validator but does not by itself make the Object observation stale.
|
||||
Validator mismatch, interface mismatch, `OperationOutcomeUnknown`, undecodable
|
||||
post-dispatch responses, and post-dispatch timeout/disconnect are displayed using
|
||||
the runtime's authoritative transition.
|
||||
|
||||
@@ -91,11 +91,7 @@ fn real_http_filesystem_workflow_is_lazy_validator_safe_and_symlink_confined() {
|
||||
.contains("68656c6c6f20776f726c64")
|
||||
);
|
||||
|
||||
// Calls conservatively stale the object even for a read operation; refresh
|
||||
// before constructing the separately confirmed write call.
|
||||
runtime.app_mut().focus = Focus::Tree;
|
||||
runtime.handle_key(InputKey::Refresh);
|
||||
settle(&mut runtime);
|
||||
assert_eq!(runtime.app().entries["/hello.txt"].state, ViewState::Fresh);
|
||||
runtime.app_mut().focus = Focus::Operations;
|
||||
|
||||
// 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"),
|
||||
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);
|
||||
|
||||
// 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().operation_cursor = 3;
|
||||
runtime.handle_key(InputKey::Enter);
|
||||
|
||||
Reference in New Issue
Block a user