client.rs (8636B)
1 use std::fs; 2 use std::path::Path; 3 4 use radroots_mesh::{RADROOTS_MESH_RETICULUM_POLICY_ID, RADROOTS_MESH_UNAVAILABLE_MESSAGE}; 5 use radroots_mesh_agent_client::{ 6 MeshAgentCapabilityAvailability, MeshAgentCapabilityMaturity, MeshAgentImplementation, 7 MeshAgentPublishRequest, MeshAgentResponseStatus, MeshAgentStatusRequest, 8 MeshAgentTransportKind, MeshAgentTransportOutcome, RADROOTS_MESH_AGENT_CLIENT_SCHEMA_ID, 9 RADROOTS_MESH_AGENT_CLIENT_SCHEMA_NAMESPACE, RadrootsMeshAgentClient, 10 RadrootsMockMeshAgentClient, 11 }; 12 use radroots_mesh_agent_proto::{ 13 RADROOTS_MESH_AGENT_SCHEMA_ID, RADROOTS_MESH_AGENT_SCHEMA_NAMESPACE, schema_sha256_hex, 14 }; 15 use radroots_transport::TransportId; 16 use radroots_transport_reticulum::{RADROOTS_RETICULUM_ENDPOINT_URI, RADROOTS_RETICULUM_SCOPE_ID}; 17 use serde_json::json; 18 19 fn publish_request() -> MeshAgentPublishRequest { 20 MeshAgentPublishRequest { 21 publish_request_id: "publish-reticulum-1".to_owned(), 22 payload_cbor: vec![1, 2, 3], 23 event_id: "event-reticulum-1".to_owned(), 24 target_fingerprint: "target-reticulum-1".to_owned(), 25 } 26 } 27 28 #[test] 29 fn mock_client_status_reports_reticulum_unavailable() { 30 let client = RadrootsMockMeshAgentClient::reticulum_unavailable(); 31 let response = client.status(MeshAgentStatusRequest { 32 include_transports: true, 33 }); 34 35 assert_eq!(client.scope().as_str(), RADROOTS_RETICULUM_SCOPE_ID); 36 assert_eq!( 37 client.policy().policy_id(), 38 RADROOTS_MESH_RETICULUM_POLICY_ID 39 ); 40 assert_eq!(response.transports.len(), 1); 41 let status = &response.transports[0]; 42 assert_eq!(status.transport, MeshAgentTransportKind::Reticulum); 43 assert_eq!(status.transport.transport_kind(), TransportId::RETICULUM); 44 assert_eq!(status.profile_id, RADROOTS_MESH_RETICULUM_POLICY_ID); 45 assert_eq!(status.endpoint_uri, RADROOTS_RETICULUM_ENDPOINT_URI); 46 assert!(status.configured); 47 assert_eq!(status.implementation, MeshAgentImplementation::Real); 48 assert_eq!(status.maturity, MeshAgentCapabilityMaturity::Preview); 49 assert_eq!( 50 status.availability, 51 MeshAgentCapabilityAvailability::Unavailable 52 ); 53 assert!(!status.usable_for_delivery); 54 assert_eq!(status.message, RADROOTS_MESH_UNAVAILABLE_MESSAGE); 55 } 56 57 #[test] 58 fn default_mock_client_uses_reticulum_unavailable_policy() { 59 let client = RadrootsMockMeshAgentClient::default(); 60 61 assert_eq!(client.scope().as_str(), RADROOTS_RETICULUM_SCOPE_ID); 62 assert_eq!( 63 client.policy().policy_id(), 64 RADROOTS_MESH_RETICULUM_POLICY_ID 65 ); 66 } 67 68 #[test] 69 fn mock_client_status_can_omit_transport_rows_without_enabling_delivery() { 70 let client = RadrootsMockMeshAgentClient::reticulum_unavailable(); 71 let response = client.status(MeshAgentStatusRequest { 72 include_transports: false, 73 }); 74 75 assert!(response.transports.is_empty()); 76 assert!(!client.policy().usable_for_delivery()); 77 } 78 79 #[test] 80 fn mock_client_publish_never_reports_success_outcomes() { 81 let client = RadrootsMockMeshAgentClient::reticulum_unavailable(); 82 let response = client.publish(publish_request()); 83 84 assert_eq!(response.publish_request_id, "publish-reticulum-1"); 85 assert_eq!(response.status, MeshAgentResponseStatus::Rejected); 86 assert_eq!(response.event_id, "event-reticulum-1"); 87 assert_eq!(response.transport_receipts.len(), 1); 88 let receipt = &response.transport_receipts[0]; 89 assert_eq!(receipt.transport_kind, MeshAgentTransportKind::Reticulum); 90 assert_eq!(receipt.endpoint_uri, RADROOTS_RETICULUM_ENDPOINT_URI); 91 assert_eq!( 92 receipt.outcome, 93 MeshAgentTransportOutcome::TransportUnavailable 94 ); 95 assert!(!receipt.outcome.is_success()); 96 assert_eq!(receipt.message, RADROOTS_MESH_UNAVAILABLE_MESSAGE); 97 for outcome in [ 98 MeshAgentTransportOutcome::Accepted, 99 MeshAgentTransportOutcome::Delivered, 100 MeshAgentTransportOutcome::Forwarded, 101 MeshAgentTransportOutcome::StoredByGateway, 102 ] { 103 assert!(outcome.is_success()); 104 assert_ne!(receipt.outcome, outcome); 105 } 106 } 107 108 #[test] 109 fn dto_schema_names_match_capnp_surface() { 110 assert_eq!(MeshAgentTransportKind::Reticulum.schema_name(), "reticulum"); 111 assert_eq!(MeshAgentImplementation::Real.schema_name(), "real"); 112 assert_eq!(MeshAgentImplementation::Mock.schema_name(), "mock"); 113 assert_eq!( 114 MeshAgentCapabilityMaturity::Preview.schema_name(), 115 "preview" 116 ); 117 assert_eq!(MeshAgentCapabilityMaturity::Stable.schema_name(), "stable"); 118 assert_eq!( 119 MeshAgentCapabilityAvailability::Available.schema_name(), 120 "available" 121 ); 122 assert_eq!( 123 MeshAgentCapabilityAvailability::Degraded.schema_name(), 124 "degraded" 125 ); 126 assert_eq!( 127 MeshAgentCapabilityAvailability::Unavailable.schema_name(), 128 "unavailable" 129 ); 130 assert_eq!(MeshAgentResponseStatus::Accepted.schema_name(), "accepted"); 131 assert_eq!(MeshAgentResponseStatus::Deferred.schema_name(), "deferred"); 132 assert_eq!(MeshAgentResponseStatus::Rejected.schema_name(), "rejected"); 133 assert_eq!( 134 MeshAgentTransportOutcome::Accepted.schema_name(), 135 "accepted" 136 ); 137 assert_eq!( 138 MeshAgentTransportOutcome::Delivered.schema_name(), 139 "delivered" 140 ); 141 assert_eq!( 142 MeshAgentTransportOutcome::Forwarded.schema_name(), 143 "forwarded" 144 ); 145 assert_eq!( 146 MeshAgentTransportOutcome::TransportUnavailable.schema_name(), 147 "transportUnavailable" 148 ); 149 assert_eq!( 150 MeshAgentTransportOutcome::StoredByGateway.schema_name(), 151 "storedByGateway" 152 ); 153 assert_eq!( 154 MeshAgentTransportOutcome::DeferredUntilImplemented.schema_name(), 155 "deferredUntilImplemented" 156 ); 157 assert_eq!( 158 MeshAgentTransportOutcome::Rejected.schema_name(), 159 "rejected" 160 ); 161 assert_eq!( 162 MeshAgentTransportOutcome::RouteUnavailable.schema_name(), 163 "routeUnavailable" 164 ); 165 assert_eq!(MeshAgentTransportOutcome::Timeout.schema_name(), "timeout"); 166 } 167 168 #[test] 169 fn serde_output_uses_schema_field_names() { 170 let request = publish_request(); 171 let request_json = serde_json::to_value(&request).expect("serialize request"); 172 173 assert_eq!( 174 request_json, 175 json!({ 176 "publishRequestId": "publish-reticulum-1", 177 "payloadCbor": [1, 2, 3], 178 "eventId": "event-reticulum-1", 179 "targetFingerprint": "target-reticulum-1" 180 }) 181 ); 182 183 let client = RadrootsMockMeshAgentClient::reticulum_unavailable(); 184 let response_json = serde_json::to_value(client.publish(request)).expect("serialize response"); 185 186 assert_eq!(response_json["publishRequestId"], "publish-reticulum-1"); 187 assert_eq!(response_json["status"], "rejected"); 188 assert_eq!( 189 response_json["transportReceipts"][0]["transportKind"], 190 "reticulum" 191 ); 192 assert_eq!( 193 response_json["transportReceipts"][0]["outcome"], 194 "transportUnavailable" 195 ); 196 } 197 198 #[test] 199 fn client_schema_hash_matches_proto_authority() { 200 let client = RadrootsMockMeshAgentClient::reticulum_unavailable(); 201 202 assert_eq!( 203 RADROOTS_MESH_AGENT_CLIENT_SCHEMA_ID, 204 RADROOTS_MESH_AGENT_SCHEMA_ID 205 ); 206 assert_eq!( 207 RADROOTS_MESH_AGENT_CLIENT_SCHEMA_NAMESPACE, 208 RADROOTS_MESH_AGENT_SCHEMA_NAMESPACE 209 ); 210 assert_eq!(client.schema_sha256_hex(), schema_sha256_hex()); 211 assert_eq!( 212 client.schema_sha256_hex(), 213 "712aaa11dfec25abf44edb3b0be447f0596442271d46a8b1d9fedb7c3df00bb2" 214 ); 215 } 216 217 #[test] 218 fn client_source_uses_schema_field_names_without_legacy_status_terms() { 219 let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); 220 let source = fs::read_to_string(manifest_dir.join("src/lib.rs")).expect("read source"); 221 222 for required in [ 223 "MeshAgentStatusRequest", 224 "include_transports", 225 "MeshAgentStatusResponse", 226 "transports", 227 "MeshAgentTransportStatus", 228 "profile_id", 229 "endpoint_uri", 230 "usable_for_delivery", 231 "MeshAgentPublishRequest", 232 "publish_request_id", 233 "payload_cbor", 234 "event_id", 235 "target_fingerprint", 236 "MeshAgentPublishResponse", 237 "transport_receipts", 238 ] { 239 assert!(source.contains(required), "{required}"); 240 } 241 for forbidden in [ 242 "readiness", 243 "implementation_state", 244 "publish_usable", 245 "fetch_usable", 246 "redacted_message", 247 "retired_noop", 248 ] { 249 assert!(!source.contains(forbidden), "{forbidden}"); 250 } 251 }