lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

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 }