fix(client): preserve operation observation ownership

This commit is contained in:
2026-09-30 09:38:28 +09:00
parent f207b71f83
commit b4e38fc2c2
2 changed files with 354 additions and 48 deletions
+217
View File
@@ -288,6 +288,45 @@ fn normalized_aliases_preserve_newer_request_ownership() {
client.object(&session, "/a/shared").unwrap().validator,
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();
client
.complete(
newer_alias.id,
fetch_response(
"/b/shared",
&object("shared", Some("shared-ref"), b"newest"),
),
)
.unwrap();
let mut contradictory = object("shared", Some("shared-ref"), b"newest");
contradictory.description = Some("contradictory representation".into());
let error = client
.complete(
older_tree.id,
encode_fetch_tree_response(
&request,
&FetchTreeResponse {
root: ObjectTreeNode {
object: object("a", None, b"root"),
children: vec![ObjectTreeNode {
object: contradictory,
children: vec![],
}],
},
truncated: false,
},
wire_limits(),
)
.unwrap(),
)
.unwrap_err();
assert_eq!(error.kind, ClientErrorKind::InvalidResponse);
assert_eq!(
client.object(&session, "/b/shared").unwrap().validator,
Some(b"newest".to_vec())
);
}
#[test]
@@ -445,6 +484,184 @@ fn call_context_fixes_membership_validators_descriptor_and_result_validation() {
);
}
#[test]
fn older_retrieval_cannot_restore_freshness_after_operation_completion() {
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", Some("shared-ref"), b"object-v1"),
);
observe_interface(
&mut client,
&session,
FetchInterfaceResponse {
interface: "math".into(),
descriptor: descriptor(TypeExpr::Integer),
validator: Some(b"interface-v1".to_vec()),
},
);
let older_alias = client.prepare_fetch(&session, "/alias/item").unwrap();
let call = client
.prepare_call(&session, "/item", "math", "calculate", args(7))
.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(14),
validator: Some(b"object-v2".to_vec()),
},
wire_limits(),
)
.unwrap(),
)
.unwrap();
assert_eq!(
client.object(&session, "/item").unwrap().state,
ObservationState::Stale
);
client
.complete(
older_alias.id,
fetch_response(
"/alias/item",
&object("item", Some("shared-ref"), b"object-v1"),
),
)
.unwrap();
let alias = client.object(&session, "/alias/item").unwrap();
assert_eq!(alias.state, ObservationState::Stale);
assert_eq!(alias.validator, Some(b"object-v2".to_vec()));
assert_eq!(
client
.prepare_call(&session, "/alias/item", "math", "calculate", args(1))
.unwrap_err()
.kind,
ClientErrorKind::MissingObservation
);
observe_object(
&mut client,
&session,
"/item",
object("item", Some("shared-ref"), b"object-v2"),
);
let older_alias = client.prepare_fetch(&session, "/second/item").unwrap();
let call = client
.prepare_call(&session, "/item", "math", "calculate", args(8))
.unwrap();
client.mark_dispatched(call.id).unwrap();
let unknown = ProtocolError {
code: ProtocolErrorCode::OperationOutcomeUnknown,
message: "commit uncertain".into(),
};
client
.complete(
call.id,
encode_protocol_error_response(
ProtocolInteraction::CallOperation,
&unknown,
wire_limits(),
)
.unwrap(),
)
.unwrap();
client
.complete(
older_alias.id,
fetch_response(
"/second/item",
&object("item", Some("shared-ref"), b"object-v2"),
),
)
.unwrap();
assert_eq!(
client.object(&session, "/second/item").unwrap().state,
ObservationState::Stale
);
}
#[test]
fn in_flight_calls_pin_target_identity_until_staleness_is_applied() {
let mut client = client(1, 8, 8, 8);
let session = client
.open_session("https://example.test/wip", SecurityContext::new("subject"))
.unwrap();
observe_object(
&mut client,
&session,
"/item",
object("item", Some("shared-ref"), b"object-v1"),
);
observe_interface(
&mut client,
&session,
FetchInterfaceResponse {
interface: "math".into(),
descriptor: descriptor(TypeExpr::Integer),
validator: Some(b"interface-v1".to_vec()),
},
);
let call = client
.prepare_call(&session, "/item", "math", "calculate", args(7))
.unwrap();
client.mark_dispatched(call.id).unwrap();
assert_eq!(
client.prepare_fetch(&session, "/other").unwrap_err().kind,
ClientErrorKind::Capacity
);
let unknown = ProtocolError {
code: ProtocolErrorCode::OperationOutcomeUnknown,
message: "commit uncertain".into(),
};
client
.complete(
call.id,
encode_protocol_error_response(
ProtocolInteraction::CallOperation,
&unknown,
wire_limits(),
)
.unwrap(),
)
.unwrap();
assert_eq!(
client.object(&session, "/item").unwrap().state,
ObservationState::Stale
);
observe_object(
&mut client,
&session,
"/other",
object("other", None, b"other"),
);
assert!(client.object(&session, "/item").is_none());
assert!(client.object(&session, "/other").is_some());
}
#[test]
fn validator_mismatch_stales_only_its_observation_and_never_retries() {
let mut client = client(8, 8, 8, 8);