use std::cell::Cell; use std::collections::BTreeMap; use std::rc::Rc; use std::time::{Duration, Instant}; use wip_host::{ CallError, CallErrorKind, CallRequest, CancellationToken, ExecutionContext, InterfaceRef, InterfaceRegistry, InterfaceTarget, ObjectProvider, OperationAttempt, ProviderResult, PublishedObject, RegisteredInterface, Scope, ScopeObservation, Target, Validator, WipObject, }; use wip_interface::{ CodecError, EffectGuarantee, HostError, OperationResult, TypeDeclaration, TypeExpr, Value, WipType, interface, }; use wip_protocol::ProtocolErrorCode; #[test] fn principal_projection_orders_subsets_and_rejects_unimplemented_or_duplicate_tokens() { let mut registry = InterfaceRegistry::new(); let root = Scope::new("/").unwrap(); let read = registry .register(&root, "reader", ReaderDefinition, None) .unwrap(); let write = registry .register(&root, "writer::#日本語", WriterDefinition, None) .unwrap(); let hidden = registry .register(&root, "hidden", HiddenDefinition, None) .unwrap(); let publication = || { PublishedObject::new(Document::open("document", None, Rc::new(Cell::new(0)))) .expose(&read) .unwrap() .expose(&write) .unwrap() }; let projected = publication() .project( &[write.reference().clone(), read.reference().clone()], |_, operation| operation == "label", ) .unwrap(); assert_eq!( projected.protocol_object().interfaces, vec![ write.reference().protocol_reference(), read.reference().protocol_reference() ] ); assert!( publication() .project(&[hidden.reference().clone()], |_, _| true) .is_err() ); assert!( publication() .project( &[read.reference().clone(), read.reference().clone()], |_, _| true ) .is_err() ); assert!( publication() .project(&[], |_, _| true) .unwrap() .protocol_object() .interfaces .is_empty() ); let current = ScopeObservation::new(&root, &Document::open("", None, Rc::new(Cell::new(0)))); let full = registry .fetch_interface(&read.reference().protocol_reference(), ¤t) .unwrap(); let filtered = full .clone() .project_operations(|operation| operation == "label"); assert_eq!(filtered.descriptor.operations.len(), 1); assert_eq!(full.descriptor.operations.len(), 3); assert_eq!( registry .fetch_interface(&read.reference().protocol_reference(), ¤t) .unwrap(), full ); } #[interface] pub trait Reader { /// Returns the reader-qualified label. fn label(&self) -> OperationResult; /// Adds two integers. fn add(&self, left: i64, right: i64) -> OperationResult; /// Reports a backend failure. fn fail(&self) -> OperationResult; } #[interface] pub trait Writer { /// Returns the writer-qualified label. fn label(&self) -> OperationResult; } #[interface] pub trait Hidden { /// Returns a hidden value. fn hidden(&self) -> OperationResult; } #[interface] pub trait Broken { /// Returns a deliberately invalid encoded value. fn broken(&self) -> OperationResult; /// Produces a result codec failure after implementation invocation. fn encode_failure(&self) -> OperationResult; /// Panics after implementation invocation begins. fn panic_after_call(&self) -> OperationResult; /// Reports an internal failure without an effect guarantee. fn unknown_internal(&self) -> OperationResult; /// Reports an internal failure with a non-commit guarantee. fn safe_internal(&self) -> OperationResult; /// Reports a resource failure without an effect guarantee. fn unknown_resource(&self) -> OperationResult; /// Reports a resource failure with a non-commit guarantee. fn safe_resource(&self) -> OperationResult; } #[derive(Debug)] pub struct BadValue; impl WipType for BadValue { fn type_expr() -> TypeExpr { TypeExpr::Integer } fn collect_declarations(_: &mut Vec) {} fn encode(self) -> Result { Ok(Value::String("not an integer".into())) } fn decode(_: Value) -> Result { Ok(Self) } } #[derive(Debug)] pub struct EncodeFailure; impl WipType for EncodeFailure { fn type_expr() -> TypeExpr { TypeExpr::String } fn collect_declarations(_: &mut Vec) {} fn encode(self) -> Result { Err(CodecError::new("private://backend/result could not encode")) } fn decode(_: Value) -> Result { Ok(Self) } } struct Document { name: String, description: Option, reference: Option, validator: Option, require_validator: bool, calls: Rc>, private_backend_handle: String, } impl Document { fn open(name: &str, reference: Option<&str>, calls: Rc>) -> Self { Self { name: name.into(), description: Some("public document".into()), reference: reference.map(str::to_owned), validator: None, require_validator: false, calls, private_backend_handle: "/private/backend/path".into(), } } fn strict(name: &str, reference: &str, validator: &[u8], calls: Rc>) -> Self { Self { name: name.into(), description: Some("public document".into()), reference: Some(reference.into()), validator: Some(Validator::new(validator)), require_validator: true, calls, private_backend_handle: "secret-handle".into(), } } fn record_call(&self) { self.calls.set(self.calls.get() + 1); } } impl WipObject for Document { fn name(&self) -> &str { &self.name } fn description(&self) -> Option<&str> { self.description.as_deref() } fn reference(&self) -> Option<&str> { self.reference.as_deref() } fn validator(&self) -> Option<&Validator> { self.validator.as_ref() } fn requires_validator(&self, _: &InterfaceRef, _: &str) -> bool { self.require_validator } } impl Reader for Document { fn label(&self) -> OperationResult { self.record_call(); Ok("reader".into()) } fn add(&self, left: i64, right: i64) -> OperationResult { self.record_call(); Ok(left + right) } fn fail(&self) -> OperationResult { self.record_call(); Err(HostError::internal( "backend unavailable", EffectGuarantee::Unknown, )) } } impl Writer for Document { fn label(&self) -> OperationResult { self.record_call(); Ok("writer".into()) } } impl Hidden for Document { fn hidden(&self) -> OperationResult { self.record_call(); Ok("hidden".into()) } } impl Broken for Document { fn broken(&self) -> OperationResult { self.record_call(); Ok(BadValue) } fn encode_failure(&self) -> OperationResult { self.record_call(); Ok(EncodeFailure) } fn panic_after_call(&self) -> OperationResult { self.record_call(); panic!("private://backend panic with credential=secret") } fn unknown_internal(&self) -> OperationResult { self.record_call(); Err(HostError::internal( "private://backend/path credential=secret", EffectGuarantee::Unknown, )) } fn safe_internal(&self) -> OperationResult { self.record_call(); Err(HostError::internal( "private://backend/path credential=secret", EffectGuarantee::NotCommitted, )) } fn unknown_resource(&self) -> OperationResult { self.record_call(); Err(HostError::resource_limit( "private scheduler deadline details", EffectGuarantee::Unknown, )) } fn safe_resource(&self) -> OperationResult { self.record_call(); Err(HostError::resource_limit( "private scheduler deadline details", EffectGuarantee::NotCommitted, )) } } struct TestProvider { root: Scope, reader: RegisteredInterface, writer: RegisteredInterface, broken: RegisteredInterface, calls: Rc>, resolutions: Cell, authorization_checks: Cell, deny: Cell, switched: Cell, confirm_rollback: Cell, provider_failure: Cell>, observed_budget: Cell>, observed_deadline: Cell>, observed_cancelled: Cell, } impl TestProvider { fn new( root: Scope, reader: RegisteredInterface, writer: RegisteredInterface, broken: RegisteredInterface, ) -> Self { Self { root, reader, writer, broken, calls: Rc::new(Cell::new(0)), resolutions: Cell::new(0), authorization_checks: Cell::new(0), deny: Cell::new(false), switched: Cell::new(false), confirm_rollback: Cell::new(false), provider_failure: Cell::new(None), observed_budget: Cell::new(None), observed_deadline: Cell::new(None), observed_cancelled: Cell::new(false), } } } impl ObjectProvider for TestProvider { type Object = Document; fn with_object( &self, context: &ExecutionContext, path: &str, operation: impl FnOnce(&PublishedObject) -> R, ) -> ProviderResult { self.observed_budget.set(context.resource_budget()); self.observed_deadline.set(context.deadline()); self.observed_cancelled .set(context.cancellation().is_cancelled()); self.resolutions.set(self.resolutions.get() + 1); let scope_object = Document::open("", None, Rc::clone(&self.calls)); let current_scope = ScopeObservation::new(&self.root, &scope_object); let (document, interfaces) = match path { "/" => (scope_object, 0), "/document" => ( Document::strict( "document", "document-ref", b"object-v1", Rc::clone(&self.calls), ), 2, ), "/open" => ( Document::open("open", Some("open-ref"), Rc::clone(&self.calls)), 2, ), "/left/document" | "/right/document" => ( Document::strict( "document", "shared-ref", b"shared-v1", Rc::clone(&self.calls), ), 2, ), "/switch" => { let switched = self.switched.replace(true); let (reference, validator) = if switched { ("second-ref", b"second".as_slice()) } else { ("first-ref", b"first".as_slice()) }; ( Document::strict("switch", reference, validator, Rc::clone(&self.calls)), 2, ) } "/empty" => (Document::open("empty", None, Rc::clone(&self.calls)), 0), "/invalid-object" => ( Document::open("wrong-name", None, Rc::clone(&self.calls)), 0, ), "/broken" => ( Document::open("broken", Some("broken-ref"), Rc::clone(&self.calls)), 3, ), _ => return ProviderResult::NotFound, }; let publication = PublishedObject::new(document); let publication = match interfaces { 0 => publication, 2 => publication .expose(&self.writer) .unwrap() .expose(&self.reader) .unwrap(), 3 => publication.expose(&self.broken).unwrap(), _ => unreachable!(), }; let publication = publication.bind_scope(current_scope); ProviderResult::Found(operation(&publication)) } fn execute_operation( &self, context: &ExecutionContext, path: &str, operation: impl FnOnce(&PublishedObject) -> OperationAttempt, ) -> ProviderResult { let result = self.with_object(context, path, operation); if let Some((resource, guarantee)) = self.provider_failure.get() { let error = if resource { HostError::resource_limit("private provider scheduler state", guarantee) } else { HostError::internal("private provider transaction path", guarantee) }; return ProviderResult::Failure(error); } if self.confirm_rollback.get() { match result { ProviderResult::Found(attempt) => ProviderResult::Found(attempt.confirm_rollback()), other => other, } } else { result } } fn authorize(&self, _: &str, _: &Document, _: &InterfaceRef, _: &str) -> bool { self.authorization_checks .set(self.authorization_checks.get() + 1); !self.deny.get() } } fn registry_and_provider() -> ( InterfaceRegistry, TestProvider, RegisteredInterface, ) { let mut registry = InterfaceRegistry::new(); let root = Scope::new("/").unwrap(); let reader = registry .register( &root, "reader", ReaderDefinition, Some(Validator::new(b"schema-v1")), ) .unwrap(); let writer = registry .register(&root, "writer::#日本語", WriterDefinition, None) .unwrap(); let hidden = registry .register(&root, "hidden", HiddenDefinition, None) .unwrap(); let broken = registry .register(&root, "broken", BrokenDefinition, None) .unwrap(); let provider = TestProvider::new(root, reader, writer, broken); (registry, provider, hidden) } fn request( path: &str, object_validator: Option<&[u8]>, reference: &InterfaceRef, interface_validator: Option<&[u8]>, operation: &str, arguments: BTreeMap, ) -> CallRequest { CallRequest { target: Target { path: path.into(), validator: object_validator.map(<[u8]>::to_vec), }, interface: InterfaceTarget { reference: reference.protocol_reference(), scope_ref: None, validator: interface_validator.map(<[u8]>::to_vec), }, operation: operation.into(), arguments, } } fn assert_failure(error: CallError, kind: CallErrorKind, code: ProtocolErrorCode) { assert_eq!(error.kind(), kind); assert_eq!(error.protocol_error().code, code); } #[test] fn registry_returns_atomic_canonical_observation_without_rewriting_descriptor() { let (registry, provider, _) = registry_and_provider(); let reference = provider.reader.reference(); assert_ne!(reference.as_str(), "Reader"); let observation = provider .fetch_interface(®istry, &reference.protocol_reference()) .unwrap(); assert_eq!(observation.reference, *reference); assert_eq!(observation.descriptor, ReaderDefinition::descriptor()); assert_eq!(observation.validator, Some(Validator::new(b"schema-v1"))); let response = observation.protocol_response().unwrap(); assert_eq!(response.interface, reference.protocol_reference()); assert_eq!(response.scope_ref, None); assert_eq!(response.descriptor, ReaderDefinition::descriptor()); assert_eq!(response.validator.as_deref(), Some(b"schema-v1".as_slice())); response.descriptor.validate().unwrap(); let mut invalid_observation = provider .fetch_interface(®istry, &reference.protocol_reference()) .unwrap(); let duplicate_operation = invalid_observation.descriptor.operations[0].clone(); invalid_observation .descriptor .operations .push(duplicate_operation); let invalid_response = invalid_observation.protocol_response().unwrap_err(); assert_eq!(invalid_response.code, ProtocolErrorCode::Internal); let error = provider .fetch_interface( ®istry, &wip_protocol::InterfaceReference { scope: "/".into(), name: "unknown".into(), }, ) .unwrap_err(); assert_eq!(error.code, ProtocolErrorCode::InterfaceNotFound); } #[test] fn canonical_object_projection_supports_optional_metadata_and_ordered_sets() { let (_, provider, _) = registry_and_provider(); let object = provider.observe_object("/document").unwrap(); assert_eq!(object.name, "document"); assert_eq!(object.description.as_deref(), Some("public document")); assert_eq!(object.r#ref.as_deref(), Some("document-ref")); assert_eq!(object.validator.as_deref(), Some(b"object-v1".as_slice())); assert_eq!( object.interfaces, vec![ provider.writer.reference().protocol_reference(), provider.reader.reference().protocol_reference(), ] ); assert!(!format!("{object:?}").contains("secret-handle")); assert!(!format!("{object:?}").contains("/document")); let empty = provider.observe_object("/empty").unwrap(); assert!(empty.interfaces.is_empty()); assert_eq!(empty.r#ref, None); assert_eq!(empty.validator, None); } #[test] fn duplicate_exposure_is_rejected_and_private_state_is_not_projected() { let (_, provider, _) = registry_and_provider(); let publication = PublishedObject::new(Document::open( "duplicate", None, Rc::clone(&provider.calls), )) .expose(&provider.reader) .unwrap(); assert_eq!( publication.implementation().private_backend_handle, "/private/backend/path" ); let error = match publication.expose(&provider.reader) { Ok(_) => panic!("duplicate exposure unexpectedly succeeded"), Err(error) => error, }; assert!( error .to_string() .contains(provider.reader.reference().as_str()) ); } #[test] fn paths_are_resolution_context_not_object_identity() { let (_, provider, _) = registry_and_provider(); let left = provider.observe_object("/left/document").unwrap(); let right = provider.observe_object("/right/document").unwrap(); assert_eq!(left, right); assert_eq!(left.r#ref.as_deref(), Some("shared-ref")); let first = provider.observe_object("/switch").unwrap(); let second = provider.observe_object("/switch").unwrap(); assert_ne!(first.r#ref, second.r#ref); assert_ne!(first.validator, second.validator); } #[test] fn path_dispatch_is_interface_qualified_and_resolves_once_per_call() { let (_, provider, _) = registry_and_provider(); let before = provider.resolutions.get(); let writer = provider .call_operation(request( "/open", None, provider.writer.reference(), None, "label", BTreeMap::new(), )) .unwrap(); let reader = provider .call_operation(request( "/open", None, provider.reader.reference(), Some(b"schema-v1"), "label", BTreeMap::new(), )) .unwrap(); let strict = provider .call_operation(request( "/document", Some(b"object-v1"), provider.reader.reference(), Some(b"schema-v1"), "label", BTreeMap::new(), )) .unwrap(); assert_eq!(writer.result, Value::String("writer".into())); assert_eq!(reader.result, Value::String("reader".into())); assert_eq!(strict.result, Value::String("reader".into())); assert_eq!(strict.validator.as_deref(), Some(b"object-v1".as_slice())); assert_eq!(provider.resolutions.get() - before, 3); assert_eq!(provider.calls.get(), 3); } #[test] fn call_failures_follow_canonical_precedence_without_execution() { let (_, provider, hidden) = registry_and_provider(); let resolutions = provider.resolutions.get(); for invalid_path in [ "relative", "/trailing/", "/double//segment", "/dot/./segment", "/parent/../segment", ] { assert_failure( provider .call_operation(request( invalid_path, Some(b"stale"), hidden.reference(), Some(b"stale"), "missing", BTreeMap::new(), )) .unwrap_err(), CallErrorKind::InvalidRequest, ProtocolErrorCode::InvalidRequest, ); } assert_eq!(provider.resolutions.get(), resolutions); assert_failure( provider.observe_object("/invalid/").unwrap_err(), CallErrorKind::InvalidRequest, ProtocolErrorCode::InvalidRequest, ); assert_eq!(provider.resolutions.get(), resolutions); assert_failure( provider .call_operation(request( "/missing", Some(b"stale"), hidden.reference(), Some(b"stale"), "missing", BTreeMap::new(), )) .unwrap_err(), CallErrorKind::NotFound, ProtocolErrorCode::NotFound, ); assert_failure( provider .call_operation(request( "/document", Some(b"stale"), hidden.reference(), None, "missing", BTreeMap::new(), )) .unwrap_err(), CallErrorKind::ValidatorMismatch, ProtocolErrorCode::ValidatorMismatch, ); assert_failure( provider .call_operation(request( "/document", Some(b"object-v1"), hidden.reference(), None, "hidden", BTreeMap::new(), )) .unwrap_err(), CallErrorKind::InterfaceMismatch, ProtocolErrorCode::InterfaceMismatch, ); assert_failure( provider .call_operation(request( "/document", Some(b"object-v1"), provider.reader.reference(), None, "missing", BTreeMap::new(), )) .unwrap_err(), CallErrorKind::InterfaceValidatorRequired, ProtocolErrorCode::InterfaceValidatorRequired, ); assert_failure( provider .call_operation(request( "/document", Some(b"object-v1"), provider.reader.reference(), Some(b"stale-schema"), "missing", BTreeMap::new(), )) .unwrap_err(), CallErrorKind::InterfaceValidatorMismatch, ProtocolErrorCode::InterfaceValidatorMismatch, ); assert_failure( provider .call_operation(request( "/document", None, provider.reader.reference(), Some(b"schema-v1"), "missing", BTreeMap::new(), )) .unwrap_err(), CallErrorKind::OperationNotFound, ProtocolErrorCode::OperationNotFound, ); assert_failure( provider .call_operation(request( "/document", None, provider.reader.reference(), Some(b"schema-v1"), "add", BTreeMap::new(), )) .unwrap_err(), CallErrorKind::ValidatorRequired, ProtocolErrorCode::ValidatorRequired, ); assert_failure( provider .call_operation(request( "/document", Some(b"object-v1"), provider.reader.reference(), Some(b"schema-v1"), "add", BTreeMap::from([("left".into(), Value::String("wrong".into()))]), )) .unwrap_err(), CallErrorKind::InvalidArguments, ProtocolErrorCode::InvalidArguments, ); provider.deny.set(true); assert_failure( provider .call_operation(request( "/document", Some(b"object-v1"), provider.reader.reference(), Some(b"schema-v1"), "label", BTreeMap::new(), )) .unwrap_err(), CallErrorKind::PermissionDenied, ProtocolErrorCode::PermissionDenied, ); assert_eq!(provider.authorization_checks.get(), 1); assert_eq!(provider.calls.get(), 0); } #[test] fn descriptor_guided_decoder_runs_inside_canonical_resolution_boundary() { let (_, provider, hidden) = registry_and_provider(); let decodes = Cell::new(0_u32); let decode_failure = || { decodes.set(decodes.get() + 1); Err::, _>(()) }; let missing = provider .call_operation_with_context_and_decoder( &ExecutionContext::new(), Target { path: "/missing".into(), validator: Some(b"stale".to_vec()), }, InterfaceTarget { reference: hidden.reference().protocol_reference(), scope_ref: None, validator: None, }, "missing".into(), |_| decode_failure(), ) .unwrap_err(); assert_eq!(missing.kind(), CallErrorKind::NotFound); assert_eq!(decodes.get(), 0); let stale = provider .call_operation_with_context_and_decoder( &ExecutionContext::new(), Target { path: "/document".into(), validator: Some(b"stale".to_vec()), }, InterfaceTarget { reference: hidden.reference().protocol_reference(), scope_ref: None, validator: None, }, "hidden".into(), |_| decode_failure(), ) .unwrap_err(); assert_eq!(stale.kind(), CallErrorKind::ValidatorMismatch); assert_eq!(decodes.get(), 0); let mismatch = provider .call_operation_with_context_and_decoder( &ExecutionContext::new(), Target { path: "/document".into(), validator: Some(b"object-v1".to_vec()), }, InterfaceTarget { reference: hidden.reference().protocol_reference(), scope_ref: None, validator: None, }, "hidden".into(), |_| decode_failure(), ) .unwrap_err(); assert_eq!(mismatch.kind(), CallErrorKind::InterfaceMismatch); assert_eq!(decodes.get(), 0); let invalid_arguments = provider .call_operation_with_context_and_decoder( &ExecutionContext::new(), Target { path: "/document".into(), validator: Some(b"object-v1".to_vec()), }, InterfaceTarget { reference: provider.reader.reference().protocol_reference(), scope_ref: None, validator: Some(b"schema-v1".to_vec()), }, "add".into(), |_| decode_failure(), ) .unwrap_err(); assert_eq!(invalid_arguments.kind(), CallErrorKind::InvalidArguments); assert_eq!(decodes.get(), 1); assert_eq!(provider.calls.get(), 0); } #[test] fn immutable_interface_rejects_a_supplied_validator() { let (_, provider, _) = registry_and_provider(); assert_failure( provider .call_operation(request( "/open", None, provider.writer.reference(), Some(b"unexpected"), "label", BTreeMap::new(), )) .unwrap_err(), CallErrorKind::InterfaceValidatorMismatch, ProtocolErrorCode::InterfaceValidatorMismatch, ); assert_eq!(provider.calls.get(), 0); } #[test] fn invalid_host_responses_and_uncertain_operation_failures_use_canonical_codes() { let (_, provider, _) = registry_and_provider(); assert_failure( provider.observe_object("/invalid-object").unwrap_err(), CallErrorKind::Internal, ProtocolErrorCode::Internal, ); assert_failure( provider .call_operation(request( "/broken", None, provider.broken.reference(), None, "broken", BTreeMap::new(), )) .unwrap_err(), CallErrorKind::OperationOutcomeUnknown, ProtocolErrorCode::OperationOutcomeUnknown, ); assert_failure( provider .call_operation(request( "/open", None, provider.reader.reference(), Some(b"schema-v1"), "fail", BTreeMap::new(), )) .unwrap_err(), CallErrorKind::OperationOutcomeUnknown, ProtocolErrorCode::OperationOutcomeUnknown, ); } fn call_broken(provider: &TestProvider, operation: &str) -> CallError { provider .call_operation(request( "/broken", None, provider.broken.reference(), None, operation, BTreeMap::new(), )) .unwrap_err() } #[test] fn typed_implementation_failures_map_only_with_a_non_commit_guarantee() { let (_, provider, _) = registry_and_provider(); let unknown_internal = call_broken(&provider, "unknown_internal"); assert_failure( unknown_internal.clone(), CallErrorKind::OperationOutcomeUnknown, ProtocolErrorCode::OperationOutcomeUnknown, ); assert!(!unknown_internal.message().contains("private://")); assert!( !unknown_internal .protocol_error() .message .contains("credential") ); assert_failure( call_broken(&provider, "safe_internal"), CallErrorKind::Internal, ProtocolErrorCode::Internal, ); assert_failure( call_broken(&provider, "unknown_resource"), CallErrorKind::OperationOutcomeUnknown, ProtocolErrorCode::OperationOutcomeUnknown, ); assert_failure( call_broken(&provider, "safe_resource"), CallErrorKind::ResourceLimitExceeded, ProtocolErrorCode::ResourceLimitExceeded, ); } #[test] fn post_invocation_result_and_panic_failures_require_provider_rollback() { for operation in ["broken", "encode_failure", "panic_after_call"] { let (_, provider, _) = registry_and_provider(); let error = call_broken(&provider, operation); assert_failure( error.clone(), CallErrorKind::OperationOutcomeUnknown, ProtocolErrorCode::OperationOutcomeUnknown, ); assert!(!error.message().contains("private://")); assert!(!error.message().contains("credential")); provider.confirm_rollback.set(true); assert_failure( call_broken(&provider, operation), CallErrorKind::Internal, ProtocolErrorCode::Internal, ); } } #[test] fn provider_failures_after_invocation_use_the_provider_effect_guarantee() { let (_, provider, _) = registry_and_provider(); provider .provider_failure .set(Some((false, EffectGuarantee::Unknown))); assert_failure( call_broken(&provider, "safe_internal"), CallErrorKind::OperationOutcomeUnknown, ProtocolErrorCode::OperationOutcomeUnknown, ); provider .provider_failure .set(Some((false, EffectGuarantee::NotCommitted))); let internal = call_broken(&provider, "unknown_internal"); assert_failure( internal.clone(), CallErrorKind::Internal, ProtocolErrorCode::Internal, ); assert!(!internal.message().contains("transaction")); provider .provider_failure .set(Some((true, EffectGuarantee::Unknown))); assert_failure( call_broken(&provider, "safe_resource"), CallErrorKind::OperationOutcomeUnknown, ProtocolErrorCode::OperationOutcomeUnknown, ); provider .provider_failure .set(Some((true, EffectGuarantee::NotCommitted))); assert_failure( call_broken(&provider, "unknown_resource"), CallErrorKind::ResourceLimitExceeded, ProtocolErrorCode::ResourceLimitExceeded, ); } #[test] fn execution_context_is_propagated_and_preflight_prevents_invocation() { let (_, provider, _) = registry_and_provider(); let deadline = Instant::now() + Duration::from_secs(60); let context = ExecutionContext::new() .with_deadline(deadline) .with_resource_budget(17); provider .call_operation_with_context( &context, request( "/open", None, provider.reader.reference(), Some(b"schema-v1"), "label", BTreeMap::new(), ), ) .unwrap(); assert_eq!(provider.observed_deadline.get(), Some(deadline)); assert_eq!(provider.observed_budget.get(), Some(17)); assert!(!provider.observed_cancelled.get()); let calls = provider.calls.get(); let cancellation = CancellationToken::new(); cancellation.cancel(); let cancelled = ExecutionContext::new().with_cancellation(cancellation); assert_failure( provider .call_operation_with_context( &cancelled, request( "/open", None, provider.reader.reference(), Some(b"schema-v1"), "label", BTreeMap::new(), ), ) .unwrap_err(), CallErrorKind::ResourceLimitExceeded, ProtocolErrorCode::ResourceLimitExceeded, ); assert_eq!(provider.calls.get(), calls); assert!(provider.observed_cancelled.get()); let expired = ExecutionContext::new().with_deadline(Instant::now() - Duration::from_secs(1)); assert_failure( provider .call_operation_with_context( &expired, request( "/open", None, provider.reader.reference(), Some(b"schema-v1"), "label", BTreeMap::new(), ), ) .unwrap_err(), CallErrorKind::ResourceLimitExceeded, ProtocolErrorCode::ResourceLimitExceeded, ); assert_eq!(provider.calls.get(), calls); } #[test] fn registry_boundaries_preserve_names_verbatim_and_match_exact_scope() { let (registry, provider, _) = registry_and_provider(); let legacy = provider.writer.reference(); let reference = legacy.protocol_reference(); assert_eq!(reference.scope, "/"); assert_eq!(reference.name, legacy.as_str()); assert_eq!(reference.name, "writer::#日本語"); assert_eq!(registry.resolve(&reference).as_ref(), Some(legacy)); let nested = wip_protocol::InterfaceReference { scope: "/open".into(), name: reference.name.clone(), }; assert!(registry.resolve(&nested).is_none()); assert_eq!( provider .fetch_interface(®istry, &nested) .unwrap_err() .code, ProtocolErrorCode::InterfaceNotFound ); let malformed = wip_protocol::InterfaceReference { scope: "relative".into(), name: reference.name, }; assert_eq!( provider .fetch_interface(®istry, &malformed) .unwrap_err() .code, ProtocolErrorCode::InvalidRequest ); } #[test] fn call_reference_shape_precedes_resolution_and_scope_membership_is_exact() { let (_, provider, _) = registry_and_provider(); for (scope, name) in [("relative", "valid"), ("/", ""), ("/bad/", "valid")] { let mut call = request( "/open", None, provider.writer.reference(), None, "label", BTreeMap::new(), ); call.interface.reference = wip_protocol::InterfaceReference { scope: scope.into(), name: name.into(), }; assert_eq!( provider.call_operation(call.clone()).unwrap_err().kind(), CallErrorKind::InvalidRequest ); assert_eq!( provider .call_operation_with_context_and_decoder( &ExecutionContext::new(), call.target, call.interface, call.operation, |_| Ok::<_, ()>(BTreeMap::new()), ) .unwrap_err() .kind(), CallErrorKind::InvalidRequest ); } assert_eq!(provider.resolutions.get(), 0); // /other is outside the target scope; /open is self but has no legacy artifact. for scope in ["/other", "/open"] { let mut call = request( "/open", None, provider.writer.reference(), None, "label", BTreeMap::new(), ); call.interface.reference.scope = scope.into(); assert_eq!( provider.call_operation(call.clone()).unwrap_err().kind(), CallErrorKind::InterfaceMismatch ); assert_eq!( provider .call_operation_with_context_and_decoder( &ExecutionContext::new(), call.target, call.interface, call.operation, |_| -> Result, ()> { panic!("must not decode") }, ) .unwrap_err() .kind(), CallErrorKind::InterfaceMismatch ); } assert_eq!(provider.calls.get(), 0); assert_eq!(provider.authorization_checks.get(), 0); }