use std::collections::BTreeMap; use wip_site_worldspace::wip_protocol::{ CallOperationRequest, CallOperationResponse, FetchInterfaceRequest, InterfaceReference, InterfaceTarget, MAX_SAFE_INTEGER, MIN_SAFE_INTEGER, ObjectObservation, ObserveRequest, ProtocolError, ProtocolErrorCode, Target, TypeExpr, Value, }; use wip_site_worldspace::{MAX_COUNTERS, Worldspace}; fn world() -> Worldspace { Worldspace::from_json(r#"{"nodes":[ {"path":"/","name":"","displayPath":"/","title":"Root","href":"/","html":"

root

"}, {"path":"/book","name":"book","parent":"/","displayPath":"/book","title":"Book","href":"/book/","html":"

book

"}, {"path":"/playground","name":"playground","parent":"/","displayPath":"/playground","title":"Playground","href":"/playground/","html":"

playground

"} ]}"#).unwrap() } fn reference(name: &str) -> InterfaceReference { InterfaceReference { scope: "/".into(), name: name.into(), } } fn call( world: &Worldspace, path: &str, interface: &str, operation: &str, arguments: BTreeMap, ) -> Result { world.call_operation(CallOperationRequest { target: Target { path: path.into(), validator: None, }, interface: InterfaceTarget { reference: reference(interface), scope_ref: None, validator: None, }, operation: operation.into(), arguments, }) } fn collection( world: &Worldspace, operation: &str, name: &str, ) -> Result { call( world, "/playground", "collection", operation, BTreeMap::from([("name".into(), Value::String(name.into()))]), ) } fn increment(world: &Worldspace, amount: i64) -> Result { call( world, "/playground/apple", "counter", "increment", BTreeMap::from([("amount".into(), Value::Integer(amount))]), ) } fn get(world: &Worldspace, name: &str) -> Value { call( world, &format!("/playground/{name}"), "counter", "get", BTreeMap::new(), ) .unwrap() .result } fn assert_initial_values(world: &Worldspace, observation: &ObjectObservation) { for child in observation.children.as_ref().unwrap() { let name = &child.object.name; assert_eq!( get(world, name), Value::Integer(if name == "apple" { 3 } else { 0 }), "counter {name} must remain unchanged" ); } } fn observe(world: &Worldspace, path: &str, depth: u32) -> ObjectObservation { world .observe(ObserveRequest { path: path.into(), depth, }) .unwrap() } #[test] fn typed_descriptors_root_scope_and_initial_snapshots() { let world = world(); let collection = world .fetch_interface(FetchInterfaceRequest { interface: reference("collection"), }) .unwrap(); let counter = world .fetch_interface(FetchInterfaceRequest { interface: reference("counter"), }) .unwrap(); assert_eq!( collection .descriptor .operations .iter() .map(|op| op.name.as_str()) .collect::>(), ["create", "remove"] ); for op in &collection.descriptor.operations { assert_eq!(op.parameters.len(), 1); assert_eq!(op.parameters[0].name, "name"); assert_eq!(op.parameters[0].r#type, TypeExpr::String); assert_eq!(op.returns.r#type, TypeExpr::Unit); } assert_eq!( counter .descriptor .operations .iter() .map(|op| op.name.as_str()) .collect::>(), ["get", "increment", "reset"] ); assert_eq!( counter.descriptor.operations[1].parameters[0].name, "amount" ); assert_eq!( counter.descriptor.operations[1].parameters[0].r#type, TypeExpr::Integer ); assert!( counter .descriptor .operations .iter() .all(|op| op.returns.r#type == TypeExpr::Integer) ); let initial = observe(&world, "/playground", 2); assert_eq!( initial.object.interfaces, [reference("document"), reference("collection")] ); let children = initial.children.unwrap(); assert_eq!( children .iter() .map(|c| (c.object.name.as_str(), c.object.description.as_deref())) .collect::>(), [ ("apple", Some("An in-memory counter.")), ("orange", Some("An in-memory counter.")), ] ); assert!( children .iter() .all(|c| c.object.interfaces == [reference("counter")] && c.children == Some(vec![])) ); assert_eq!(get(&world, "apple"), Value::Integer(3)); assert_eq!(get(&world, "orange"), Value::Integer(0)); assert_eq!( call( &world, "/playground/apple", "document", "read", BTreeMap::new() ) .unwrap_err() .code, ProtocolErrorCode::InterfaceMismatch ); assert_eq!(get(&world, "apple"), Value::Integer(3)); assert_eq!( call(&world, "/book", "collection", "create", BTreeMap::new()) .unwrap_err() .code, ProtocolErrorCode::InterfaceMismatch ); assert_eq!(get(&world, "apple"), Value::Integer(3)); } #[test] fn mutations_preserve_metadata_and_publish_values_and_children_without_mutating_documents() { let world = world(); let before = observe(&world, "/playground/apple", 1); let parent_before = observe(&world, "/playground", 2); let documents: Vec<_> = ["/", "/book", "/playground"] .into_iter() .map(|path| { ( path, call(&world, path, "document", "read", BTreeMap::new()) .unwrap() .result, ) }) .collect(); assert_eq!(increment(&world, 7).unwrap().result, Value::Integer(10)); assert_eq!(get(&world, "apple"), Value::Integer(10)); assert_eq!(observe(&world, "/playground/apple", 1), before); assert_eq!(observe(&world, "/playground", 2), parent_before); assert_eq!(increment(&world, -12).unwrap().result, Value::Integer(-2)); assert_eq!(get(&world, "apple"), Value::Integer(-2)); assert_eq!( call( &world, "/playground/apple", "counter", "reset", BTreeMap::new() ) .unwrap() .result, Value::Integer(0) ); assert_eq!(get(&world, "apple"), Value::Integer(0)); assert_eq!(observe(&world, "/playground/apple", 1), before); assert_eq!(observe(&world, "/playground", 2), parent_before); assert_eq!( collection(&world, "create", "New_counter-2") .unwrap() .result, Value::Unit ); assert_eq!( observe(&world, "/playground/New_counter-2", 1) .object .description .as_deref(), Some("An in-memory counter.") ); assert_eq!(get(&world, "New_counter-2"), Value::Integer(0)); collection(&world, "remove", "apple").unwrap(); assert_eq!( call( &world, "/playground/apple", "counter", "get", BTreeMap::new() ) .unwrap_err() .code, ProtocolErrorCode::NotFound ); assert!( !observe(&world, "/playground", 1) .children .unwrap() .iter() .any(|child| child.object.name == "apple") ); collection(&world, "create", "apple").unwrap(); assert_eq!(observe(&world, "/playground/apple", 1), before); assert_eq!(get(&world, "apple"), Value::Integer(0)); for (path, expected) in documents { assert_eq!( call(&world, path, "document", "read", BTreeMap::new()) .unwrap() .result, expected ); } } #[test] fn invalid_names_duplicates_missing_and_capacity_fail_without_mutation() { let world = world(); let before = observe(&world, "/", 3); let counters_before = observe(&world, "/playground", 1); for name in [ "", ".", "..", "a/b", "a b", "a\nb", "