use std::collections::BTreeMap; use wip_host::CallErrorKind; use wip_interface::{Entry, JsonMember, JsonValue, WipType}; use wip_protocol::{INTERFACE_FORMAT_V1, InterfaceDescriptor, Object, ProtocolErrorCode, Value}; use wip_public_api_example::{CatalogDefinition, PublishOutcome, PublishRequest, Visibility, run}; #[test] fn public_crates_support_the_complete_qualified_end_to_end_scenario() { let report = run().unwrap(); assert_eq!(report.object_name, "article-service"); assert_eq!( report.object_reference.as_deref(), Some("article-service-instance") ); assert_eq!( report.object_validator.as_deref(), Some(b"object-v1".as_slice()) ); assert_eq!( report.published_interfaces, [ report.catalog_interface_reference.clone(), report.audit_interface_reference.clone(), ], "interfaces must retain explicit publication order" ); assert_ne!( report.catalog_interface_reference, report.audit_interface_reference, "interfaces form an ordered set of distinct scope/name references" ); assert_eq!(report.catalog_interface_reference.scope, "/article-service"); assert_eq!(report.catalog_interface_reference.name, "catalog"); assert_eq!(report.audit_interface_reference.scope, "/article-service"); assert_eq!(report.audit_interface_reference.name, "audit"); assert_eq!(report.catalog_descriptor_format, INTERFACE_FORMAT_V1); assert_eq!(report.audit_descriptor_format, INTERFACE_FORMAT_V1); assert_eq!( report.catalog_scope_ref.as_deref(), Some("article-service-instance") ); assert_eq!(report.audit_scope_ref, report.catalog_scope_ref); assert_eq!(report.scope_mismatch, ProtocolErrorCode::InterfaceMismatch); assert_eq!( report.published, PublishOutcome::Published("article-1".into()) ); assert_eq!( report.rejected, PublishOutcome::Rejected("a title is required".into()) ); assert_eq!(report.catalog_status, "catalog-ready"); assert_eq!(report.audit_status, "audit-ready"); } #[test] fn domain_host_permission_and_validator_failures_remain_distinct() { let report = run().unwrap(); assert!(matches!(report.rejected, PublishOutcome::Rejected(_))); assert_eq!(report.host_failure, CallErrorKind::Internal); assert_eq!(report.protocol_failure.code, ProtocolErrorCode::Internal); assert_eq!( report.unknown_protocol_failure.code, ProtocolErrorCode::OperationOutcomeUnknown ); assert_eq!( report.permission_failure, ProtocolErrorCode::PermissionDenied ); assert_eq!( report.validator_failure, ProtocolErrorCode::ValidatorMismatch, "validator recovery signals must not collapse to InvalidRequest" ); } #[test] fn interface_mismatch_is_not_interface_not_found() { let report = run().unwrap(); assert_eq!( report.interface_mismatch, ProtocolErrorCode::InterfaceMismatch ); assert_eq!( report.interface_not_found, ProtocolErrorCode::InterfaceNotFound ); assert_ne!(report.interface_mismatch, report.interface_not_found); } #[test] fn invalid_path_keeps_canonical_failure_precedence() { let report = run().unwrap(); assert_eq!(report.precedence_failure, ProtocolErrorCode::InvalidRequest); assert_ne!(report.precedence_failure, report.validator_failure); assert_ne!(report.precedence_failure, report.interface_mismatch); } #[test] fn object_fields_keep_path_context_and_optional_ref_separate() { // Listing every field makes this test fail to compile if a mandatory // ObjectId is reintroduced into the canonical object DTO. let object = Object { name: "article".into(), description: None, interfaces: vec![root_reference("catalog-ref"), root_reference("audit-ref")], r#ref: None, validator: None, }; object.validate_at_path("/library/article").unwrap(); assert_eq!(object.name, "article"); assert_eq!(object.r#ref, None); assert_eq!( object.interfaces, [root_reference("catalog-ref"), root_reference("audit-ref")] ); } #[test] fn interface_reference_is_observed_separately_from_descriptor_fields() { // An exact destructuring pattern is compile-time coverage that the // descriptor has no trait-name or opaque-reference `name` field. let InterfaceDescriptor { format, documentation, types, operations, } = CatalogDefinition::descriptor(); assert_eq!(format, INTERFACE_FORMAT_V1); assert!(documentation.is_some()); assert!(!types.is_empty()); assert_eq!( operations .iter() .map(|operation| operation.name.as_str()) .collect::>(), [ "publish", "status", "backend_status", "uncertain_backend_status", "restricted_status", ] ); } #[test] fn application_record_enum_union_entry_and_json_use_schema_neutral_values() { let request = PublishRequest { title: "Canonical values".into(), body: None, source: Entry::new("/drafts/canonical-values").unwrap(), visibility: Visibility::Public, metadata: JsonValue::Object(vec![ JsonMember { name: "tags".into(), value: JsonValue::Array(vec![JsonValue::String("wip".into()), JsonValue::Null]), }, JsonMember { name: "ready".into(), value: JsonValue::Boolean(true), }, ]), }; let encoded = request.encode().unwrap(); assert_schema_neutral(&encoded); let Value::Record(fields) = encoded else { panic!("derived DTO record must use Value::Record") }; assert_eq!( fields.get("title"), Some(&Value::String("Canonical values".into())) ); assert!(!fields.contains_key("body")); assert_eq!( fields.get("source"), Some(&Value::String("/drafts/canonical-values".into())) ); assert_eq!( fields.get("visibility"), Some(&Value::String("Public".into())) ); assert_eq!( fields.get("metadata"), Some(&Value::Record(BTreeMap::from([ ("ready".into(), Value::Boolean(true)), ( "tags".into(), Value::List(vec![Value::String("wip".into()), Value::Unit]) ), ]))) ); let outcome = PublishOutcome::Rejected("policy".into()).encode().unwrap(); assert_schema_neutral(&outcome); assert_eq!( outcome, Value::Record(BTreeMap::from([ ("$case".into(), Value::String("Rejected".into())), ("value".into(), Value::String("policy".into())), ])) ); } #[test] fn entry_is_a_validated_canonical_absolute_path_string() { let entry = Entry::new("/articles/one").unwrap(); assert_eq!( entry.encode().unwrap(), Value::String("/articles/one".into()) ); for invalid in [ "articles/one", "/articles/", "/articles//one", "/articles/./one", "/articles/../one", ] { assert!( Entry::new(invalid).is_err(), "accepted invalid path {invalid}" ); assert!( Entry::decode(Value::String(invalid.into())).is_err(), "decoded invalid path {invalid}" ); } } fn assert_schema_neutral(value: &Value) { // This exhaustive match is a compile-time regression guard: reintroducing // semantic Entry/Enum/Union/Json variants makes the consumer non-exhaustive. match value { Value::Unit | Value::Boolean(_) | Value::Integer(_) | Value::Number(_) | Value::String(_) | Value::Bytes(_) => {} Value::Record(fields) => { for value in fields.values() { assert_schema_neutral(value); } } Value::List(values) => { for value in values { assert_schema_neutral(value); } } } } #[test] fn scoped_dtos_keep_identity_binding_and_validators_separate() { // Exact literals/destructuring guard the public Rust contract, including // omission of refs rather than requiring a fabricated identity. let reference = wip_protocol::InterfaceReference { scope: "/article-service".into(), name: "catalog::read#raw".into(), }; let request = wip_protocol::FetchInterfaceRequest { interface: reference.clone(), }; request.validate().unwrap(); let wip_protocol::FetchInterfaceResponse { interface, scope_ref, descriptor, validator, } = wip_protocol::FetchInterfaceResponse { interface: reference.clone(), scope_ref: None, descriptor: CatalogDefinition::descriptor(), validator: None, }; let target = wip_protocol::InterfaceTarget { reference: interface, scope_ref, validator, }; assert_eq!(target.reference, reference); assert!(target.scope_ref.is_none()); assert!(target.validator.is_none()); assert_eq!(descriptor.format, INTERFACE_FORMAT_V1); } fn root_reference(name: &str) -> wip_protocol::InterfaceReference { wip_protocol::InterfaceReference { scope: "/".into(), name: name.into(), } }