commit 3388d3404b709628bfd30088be04802dbe0134da
parent c613bec538cc83523f456b467d86496a9b2b83ac
Author: triesap <tyson@radroots.org>
Date: Tue, 7 Jul 2026 20:20:52 +0000
transport: enforce Reticulum preview endpoint exactness
- add the shared Reticulum preview endpoint constant
- reject non-exact Reticulum target URIs before canonicalization
- consume the shared endpoint in preview and publish protocol crates
- cover endpoint drift with transport, protocol, and source-boundary tests
Diffstat:
8 files changed, 177 insertions(+), 55 deletions(-)
diff --git a/crates/transport/src/lib.rs b/crates/transport/src/lib.rs
@@ -17,7 +17,9 @@ pub use delivery::{
};
pub use error::RadrootsTransportError;
pub use kind::{RadrootsTransportImplementationState, RadrootsTransportKind};
-pub use message::RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE;
+pub use message::{
+ RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI, RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE,
+};
pub use status::{RadrootsTransportDeliveryTargetStatus, RadrootsTransportOutcome};
pub use target::{
RadrootsTransportTarget, RadrootsTransportTargetFingerprint, RadrootsTransportTargetSet,
diff --git a/crates/transport/src/message.rs b/crates/transport/src/message.rs
@@ -1,3 +1,5 @@
+pub const RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI: &str = "reticulum:preview-unavailable";
+
pub const RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE: &str = concat!(
"Reticulum transport is configured for future compatibility, ",
"but this build does not implement Reticulum delivery."
diff --git a/crates/transport/src/target.rs b/crates/transport/src/target.rs
@@ -1,4 +1,6 @@
-use crate::{RadrootsTransportError, RadrootsTransportKind};
+use crate::{
+ RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI, RadrootsTransportError, RadrootsTransportKind,
+};
use alloc::collections::BTreeSet;
use alloc::format;
use alloc::string::{String, ToString};
@@ -82,7 +84,13 @@ impl RadrootsTransportTarget {
kind: RadrootsTransportKind,
uri: impl AsRef<str>,
) -> Result<Self, RadrootsTransportError> {
- let uri = RadrootsTransportTargetUri::parse(uri)?;
+ let raw_uri = uri.as_ref();
+ if kind == RadrootsTransportKind::Reticulum
+ && raw_uri != RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI
+ {
+ return Err(RadrootsTransportError::InvalidTargetUri);
+ }
+ let uri = RadrootsTransportTargetUri::parse(raw_uri)?;
let fingerprint = RadrootsTransportTargetFingerprint::from_target(&kind, &uri);
Ok(Self {
kind,
diff --git a/crates/transport/tests/source_boundary.rs b/crates/transport/tests/source_boundary.rs
@@ -68,11 +68,12 @@ fn transport_hardening_sources_reject_removed_protocol_identifiers() {
for relative_root in TRANSPORT_HARDENING_CRATE_SOURCE_ROOTS {
for path in rust_source_files(crates_root.join(relative_root).as_path()) {
- let source = read_source(path.as_path());
+ let source_raw = read_source(path.as_path());
+ let source = production_source(source_raw.as_str());
let relative_path = relative_path(crates_root, path.as_path());
for concept in FORBIDDEN_TRANSPORT_CONCEPTS {
- if contains_forbidden_concept(source.as_str(), concept.pattern) {
+ if contains_forbidden_concept(source, concept.pattern) {
findings.push(format!(
"{} contains removed transport concept `{}`: {}",
relative_path, concept.pattern, concept.reason
@@ -80,7 +81,7 @@ fn transport_hardening_sources_reject_removed_protocol_identifiers() {
}
}
- for line in removed_reticulum_preview_endpoint_lines(source.as_str()) {
+ for line in removed_reticulum_preview_endpoint_lines(source) {
findings.push(format!(
"{relative_path}:{line} contains removed Reticulum preview endpoint `reticulum:preview`"
));
@@ -97,13 +98,14 @@ fn transport_hardening_sources_reject_removed_protocol_identifiers() {
#[test]
fn transport_publish_capabilities_keep_readiness_and_usability_fields() {
- let source = read_source(
+ let source_raw = read_source(
Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("transport crate parent")
.join("transport_publish_protocol/src/lib.rs")
.as_path(),
);
+ let source = production_source(source_raw.as_str());
for required in [
"pub implementation_state: TransportPublishImplementationState,",
@@ -121,7 +123,7 @@ fn transport_publish_capabilities_keep_readiness_and_usability_fields() {
}
#[test]
-fn transport_hardening_sources_keep_proxy_and_reticulum_message_contracts() {
+fn transport_hardening_sources_keep_proxy_and_reticulum_contracts() {
let crates_root = Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("transport crate parent");
@@ -140,6 +142,8 @@ fn transport_hardening_sources_keep_proxy_and_reticulum_message_contracts() {
let transport_message_source =
read_source(crates_root.join("transport/src/message.rs").as_path());
for required in [
+ "RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI",
+ "reticulum:preview-unavailable",
"RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE",
"Reticulum transport is configured for future compatibility, ",
"but this build does not implement Reticulum delivery.",
@@ -153,6 +157,14 @@ fn transport_hardening_sources_keep_proxy_and_reticulum_message_contracts() {
let reticulum_source =
read_source(crates_root.join("transport_reticulum/src/lib.rs").as_path());
assert!(
+ reticulum_source.contains("RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI"),
+ "Reticulum preview source must consume the shared endpoint URI constant"
+ );
+ assert!(
+ !reticulum_source.contains("reticulum:preview-unavailable"),
+ "Reticulum preview source must not duplicate the shared endpoint URI"
+ );
+ assert!(
reticulum_source.contains("RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE"),
"Reticulum preview source must consume the shared unavailable message constant"
);
@@ -161,11 +173,20 @@ fn transport_hardening_sources_keep_proxy_and_reticulum_message_contracts() {
"Reticulum preview source must not duplicate the shared unavailable message"
);
- let protocol_source = read_source(
+ let protocol_source_raw = read_source(
crates_root
.join("transport_publish_protocol/src/lib.rs")
.as_path(),
);
+ let protocol_source = production_source(protocol_source_raw.as_str());
+ assert!(
+ protocol_source.contains("RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI"),
+ "transport publish protocol must consume the shared Reticulum endpoint URI constant"
+ );
+ assert!(
+ !protocol_source.contains("reticulum:preview-unavailable"),
+ "transport publish protocol must not duplicate the shared endpoint URI"
+ );
assert!(
protocol_source.contains("RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE"),
"transport publish capabilities must consume the shared Reticulum unavailable message"
@@ -202,6 +223,12 @@ fn read_source(path: &Path) -> String {
.unwrap_or_else(|error| panic!("failed to read source {}: {error}", path.display()))
}
+fn production_source(source: &str) -> &str {
+ source
+ .find("\n#[cfg(test)]")
+ .map_or(source, |index| &source[..index])
+}
+
fn relative_path(root: &Path, path: &Path) -> String {
path.strip_prefix(root)
.expect("source path is under crate root")
diff --git a/crates/transport/tests/transport.rs b/crates/transport/tests/transport.rs
@@ -1,10 +1,10 @@
use radroots_transport::{
- RadrootsTransportDeliveryReceipt, RadrootsTransportDeliveryRequest,
- RadrootsTransportDeliveryTargetStatus, RadrootsTransportError, RadrootsTransportKind,
- RadrootsTransportOutcome, RadrootsTransportSatisfactionClass,
- RadrootsTransportSatisfactionPolicy, RadrootsTransportTarget,
- RadrootsTransportTargetFingerprint, RadrootsTransportTargetReceipt, RadrootsTransportTargetSet,
- RadrootsTransportTargetUri,
+ RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI, RadrootsTransportDeliveryReceipt,
+ RadrootsTransportDeliveryRequest, RadrootsTransportDeliveryTargetStatus,
+ RadrootsTransportError, RadrootsTransportKind, RadrootsTransportOutcome,
+ RadrootsTransportSatisfactionClass, RadrootsTransportSatisfactionPolicy,
+ RadrootsTransportTarget, RadrootsTransportTargetFingerprint, RadrootsTransportTargetReceipt,
+ RadrootsTransportTargetSet, RadrootsTransportTargetUri,
};
#[test]
@@ -17,7 +17,7 @@ fn target_fingerprints_are_stable_and_transport_scoped() {
.expect("nostr target");
let reticulum = RadrootsTransportTarget::new(
RadrootsTransportKind::Reticulum,
- "wss://relay.example/Events",
+ RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI,
)
.expect("reticulum target");
@@ -161,7 +161,7 @@ fn satisfaction_policy_counts_target_statuses() {
fn deferred_transport_outcomes_are_terminal_but_not_satisfied() {
let target = RadrootsTransportTarget::new(
RadrootsTransportKind::Reticulum,
- "reticulum:preview-unavailable",
+ RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI,
)
.expect("target");
let receipt = RadrootsTransportDeliveryReceipt {
@@ -308,6 +308,33 @@ fn transport_kind_and_target_parsers_cover_negative_edges() {
}
#[test]
+fn reticulum_transport_targets_require_exact_preview_endpoint() {
+ let target = RadrootsTransportTarget::new(
+ RadrootsTransportKind::Reticulum,
+ RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI,
+ )
+ .expect("exact Reticulum preview endpoint");
+ assert_eq!(target.uri.as_str(), RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI);
+
+ for invalid in [
+ " reticulum:preview-unavailable",
+ "reticulum:preview-unavailable ",
+ "RETICULUM:preview-unavailable",
+ "reticulum:Preview-Unavailable",
+ "reticulum:preview",
+ "reticulum:preview-unavailable-alt",
+ "reticulum:custom",
+ "wss://relay.example/Events",
+ ] {
+ assert_eq!(
+ RadrootsTransportTarget::new(RadrootsTransportKind::Reticulum, invalid)
+ .expect_err("invalid Reticulum preview endpoint"),
+ RadrootsTransportError::InvalidTargetUri
+ );
+ }
+}
+
+#[test]
fn target_fingerprints_and_sets_cover_accessors_and_validation() {
let target = RadrootsTransportTarget::new(RadrootsTransportKind::Mesh, "mesh://node.example")
.expect("mesh target");
diff --git a/crates/transport_publish_protocol/src/lib.rs b/crates/transport_publish_protocol/src/lib.rs
@@ -11,7 +11,8 @@ use std::{string::String, vec::Vec};
use core::fmt;
use radroots_transport::{
- RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE, RadrootsTransportError, RadrootsTransportKind,
+ RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI, RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE,
+ RadrootsTransportError, RadrootsTransportKind,
};
pub const API_VERSION: &str = "radrootsd.transport_publish.v2";
@@ -20,7 +21,6 @@ pub const METHOD_CAPABILITIES: &str = "transport.publish.capabilities";
pub const METHOD_EVENT: &str = "transport.publish.event";
pub const METHOD_JOB_GET: &str = "transport.publish.job.get";
pub const METHOD_JOB_LIST: &str = "transport.publish.job.list";
-pub const RETICULUM_PREVIEW_ENDPOINT_URI: &str = "reticulum:preview-unavailable";
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TransportPublishProtocolError {
@@ -81,7 +81,7 @@ impl fmt::Display for TransportPublishProtocolError {
}
Self::InvalidReticulumPreviewEndpoint { index } => write!(
f,
- "transport target {index} Reticulum preview endpoint must be {RETICULUM_PREVIEW_ENDPOINT_URI}"
+ "transport target {index} Reticulum preview endpoint must be {RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI}"
),
Self::TargetLimitExceeded { max, actual } => {
write!(f, "transport target count {actual} exceeds limit {max}")
@@ -166,7 +166,7 @@ impl TransportPublishTarget {
pub fn reticulum_preview(behavior: TransportPublishPreviewBehavior) -> Self {
Self {
transport_kind: "reticulum".to_owned(),
- endpoint_uri: RETICULUM_PREVIEW_ENDPOINT_URI.to_owned(),
+ endpoint_uri: RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI.to_owned(),
preview_behavior: Some(behavior),
}
}
@@ -181,7 +181,7 @@ impl TransportPublishTarget {
return Err(TransportPublishProtocolError::EmptyEndpointUri { index });
}
if transport_kind == RadrootsTransportKind::Reticulum
- && self.endpoint_uri != RETICULUM_PREVIEW_ENDPOINT_URI
+ && self.endpoint_uri != RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI
{
return Err(TransportPublishProtocolError::InvalidReticulumPreviewEndpoint { index });
}
@@ -776,7 +776,7 @@ mod tests {
noncanonical_reticulum_kind.target_policy =
TransportPublishTargetPolicy::explicit_targets(vec![TransportPublishTarget {
transport_kind: "Reticulum".to_owned(),
- endpoint_uri: RETICULUM_PREVIEW_ENDPOINT_URI.to_owned(),
+ endpoint_uri: RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI.to_owned(),
preview_behavior: Some(TransportPublishPreviewBehavior::RejectDeliveryAttempts),
}]);
assert_eq!(
@@ -784,6 +784,27 @@ mod tests {
Err(TransportPublishProtocolError::InvalidTransportKind { index: 0 })
);
+ for invalid in [
+ " reticulum:preview-unavailable",
+ "reticulum:preview-unavailable ",
+ "RETICULUM:preview-unavailable",
+ "reticulum:Preview-Unavailable",
+ "reticulum:preview",
+ "reticulum:custom",
+ ] {
+ let mut invalid_reticulum_endpoint = request.clone();
+ invalid_reticulum_endpoint.target_policy =
+ TransportPublishTargetPolicy::explicit_targets(vec![TransportPublishTarget {
+ transport_kind: "reticulum".to_owned(),
+ endpoint_uri: invalid.to_owned(),
+ preview_behavior: Some(TransportPublishPreviewBehavior::RejectDeliveryAttempts),
+ }]);
+ assert_eq!(
+ invalid_reticulum_endpoint.validate(1),
+ Err(TransportPublishProtocolError::InvalidReticulumPreviewEndpoint { index: 0 })
+ );
+ }
+
let mut removed_proxy_kind = request.clone();
removed_proxy_kind.target_policy =
TransportPublishTargetPolicy::explicit_targets(vec![TransportPublishTarget {
diff --git a/crates/transport_reticulum/src/lib.rs b/crates/transport_reticulum/src/lib.rs
@@ -8,14 +8,14 @@ use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
use radroots_transport::{
- RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE, RadrootsTransportDeliveryReceipt,
- RadrootsTransportDeliveryRequest, RadrootsTransportDeliveryTargetStatus,
- RadrootsTransportImplementationState, RadrootsTransportKind, RadrootsTransportOutcome,
- RadrootsTransportTarget, RadrootsTransportTargetReceipt,
+ RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI, RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE,
+ RadrootsTransportDeliveryReceipt, RadrootsTransportDeliveryRequest,
+ RadrootsTransportDeliveryTargetStatus, RadrootsTransportImplementationState,
+ RadrootsTransportKind, RadrootsTransportOutcome, RadrootsTransportTarget,
+ RadrootsTransportTargetReceipt,
};
const DEFAULT_PROFILE_ID: &str = "transport.reticulum.preview";
-pub const RETICULUM_PREVIEW_ENDPOINT_URI: &str = "reticulum:preview-unavailable";
const UNAVAILABLE_CODE: &str = "transport_unavailable";
const DEFERRED_CODE: &str = "deferred_until_implemented";
const DEFERRED_MESSAGE: &str = "Reticulum preview delivery is deferred until implementation";
@@ -47,12 +47,12 @@ pub struct RadrootsReticulumPreviewEndpoint {
impl RadrootsReticulumPreviewEndpoint {
pub fn parse(raw: impl AsRef<str>) -> Result<Self, RadrootsReticulumPreviewError> {
- let uri = raw.as_ref().trim();
- if uri != RETICULUM_PREVIEW_ENDPOINT_URI {
+ let uri = raw.as_ref();
+ if uri != RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI {
return Err(RadrootsReticulumPreviewError::InvalidEndpoint);
}
Ok(Self {
- uri: RETICULUM_PREVIEW_ENDPOINT_URI.to_owned(),
+ uri: RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI.to_owned(),
})
}
@@ -67,7 +67,8 @@ impl RadrootsReticulumPreviewEndpoint {
impl Default for RadrootsReticulumPreviewEndpoint {
fn default() -> Self {
- Self::parse(RETICULUM_PREVIEW_ENDPOINT_URI).expect("default Reticulum preview endpoint")
+ Self::parse(RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI)
+ .expect("default Reticulum preview endpoint")
}
}
@@ -274,7 +275,7 @@ fn ensure_reticulum_targets(
if target.kind != RadrootsTransportKind::Reticulum {
return Err(RadrootsReticulumPreviewError::NonReticulumTarget);
}
- if target.uri.as_str() != RETICULUM_PREVIEW_ENDPOINT_URI {
+ if target.uri.as_str() != RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI {
return Err(RadrootsReticulumPreviewError::InvalidEndpoint);
}
}
diff --git a/crates/transport_reticulum/tests/reticulum_preview.rs b/crates/transport_reticulum/tests/reticulum_preview.rs
@@ -1,7 +1,8 @@
use radroots_transport::{
- RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE, RadrootsTransportDeliveryRequest,
- RadrootsTransportDeliveryTargetStatus, RadrootsTransportImplementationState,
- RadrootsTransportKind, RadrootsTransportSatisfactionClass, RadrootsTransportSatisfactionPolicy,
+ RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI, RADROOTS_RETICULUM_UNAVAILABLE_MESSAGE,
+ RadrootsTransportDeliveryRequest, RadrootsTransportDeliveryTargetStatus,
+ RadrootsTransportImplementationState, RadrootsTransportKind,
+ RadrootsTransportSatisfactionClass, RadrootsTransportSatisfactionPolicy,
RadrootsTransportTarget, RadrootsTransportTargetSet,
};
use radroots_transport_reticulum::{
@@ -35,7 +36,10 @@ fn default_profile_is_configured_preview_unavailable_and_rejecting() {
let status = profile.status();
assert_eq!(profile.profile_id(), "transport.reticulum.preview");
- assert_eq!(profile.endpoint().as_str(), "reticulum:preview-unavailable");
+ assert_eq!(
+ profile.endpoint().as_str(),
+ RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI
+ );
assert_eq!(
profile.behavior(),
RadrootsReticulumPreviewBehavior::RejectDeliveryAttempts
@@ -52,17 +56,20 @@ fn default_profile_is_configured_preview_unavailable_and_rejecting() {
#[test]
fn endpoint_and_profile_validation_are_strict_and_canonical() {
- let endpoint =
- RadrootsReticulumPreviewEndpoint::parse("reticulum:preview-unavailable").expect("endpoint");
- assert_eq!(endpoint.as_str(), "reticulum:preview-unavailable");
- assert_eq!(endpoint.to_string(), "reticulum:preview-unavailable");
+ let endpoint = RadrootsReticulumPreviewEndpoint::parse(RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI)
+ .expect("endpoint");
+ assert_eq!(endpoint.as_str(), RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI);
+ assert_eq!(
+ endpoint.to_string(),
+ RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI
+ );
assert_eq!(
endpoint.clone().into_string(),
- "reticulum:preview-unavailable"
+ RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI
);
assert_eq!(
RadrootsReticulumPreviewEndpoint::default().as_str(),
- "reticulum:preview-unavailable"
+ RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI
);
assert_eq!(
@@ -83,6 +90,16 @@ fn endpoint_and_profile_validation_are_strict_and_canonical() {
RadrootsReticulumPreviewError::InvalidEndpoint
);
assert_eq!(
+ RadrootsReticulumPreviewEndpoint::parse(" reticulum:preview-unavailable")
+ .expect_err("leading whitespace endpoint"),
+ RadrootsReticulumPreviewError::InvalidEndpoint
+ );
+ assert_eq!(
+ RadrootsReticulumPreviewEndpoint::parse("reticulum:preview-unavailable ")
+ .expect_err("trailing whitespace endpoint"),
+ RadrootsReticulumPreviewError::InvalidEndpoint
+ );
+ assert_eq!(
RadrootsReticulumPreviewEndpoint::parse("reticulum:custom").expect_err("custom endpoint"),
RadrootsReticulumPreviewError::InvalidEndpoint
);
@@ -121,7 +138,10 @@ fn endpoint_and_profile_validation_are_strict_and_canonical() {
)
.expect("custom behavior profile");
assert_eq!(profile.profile_id(), "transport.reticulum.custom");
- assert_eq!(profile.endpoint().as_str(), "reticulum:preview-unavailable");
+ assert_eq!(
+ profile.endpoint().as_str(),
+ RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI
+ );
assert_eq!(
profile.behavior(),
RadrootsReticulumPreviewBehavior::DeferDeliveryPlans
@@ -131,7 +151,9 @@ fn endpoint_and_profile_validation_are_strict_and_canonical() {
#[test]
fn reject_delivery_attempts_returns_unavailable_without_success_or_nostr_routing() {
let transport = RadrootsReticulumPreviewTransport::default();
- let request = delivery_request(vec![reticulum_target("reticulum:preview-unavailable")]);
+ let request = delivery_request(vec![reticulum_target(
+ RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI,
+ )]);
let receipt = transport.deliver(request).expect("delivery receipt");
assert_eq!(receipt.target_receipts.len(), 1);
@@ -158,14 +180,21 @@ fn reject_delivery_attempts_returns_unavailable_without_success_or_nostr_routing
#[test]
fn noncanonical_reticulum_preview_targets_are_rejected() {
- let transport = RadrootsReticulumPreviewTransport::default();
- let err = transport
- .deliver(delivery_request(vec![reticulum_target(
- "reticulum:preview-unavailable-alt",
- )]))
- .expect_err("noncanonical Reticulum endpoint");
-
- assert_eq!(err, RadrootsReticulumPreviewError::InvalidEndpoint);
+ for invalid in [
+ " reticulum:preview-unavailable",
+ "reticulum:preview-unavailable ",
+ "RETICULUM:preview-unavailable",
+ "reticulum:Preview-Unavailable",
+ "reticulum:preview",
+ "reticulum:preview-unavailable-alt",
+ "reticulum:custom",
+ ] {
+ assert_eq!(
+ RadrootsTransportTarget::new(RadrootsTransportKind::Reticulum, invalid)
+ .expect_err("noncanonical Reticulum target"),
+ radroots_transport::RadrootsTransportError::InvalidTargetUri
+ );
+ }
}
#[test]
@@ -174,7 +203,9 @@ fn deferred_delivery_plan_mode_never_counts_as_satisfied() {
RadrootsReticulumPreviewProfile::default()
.with_behavior(RadrootsReticulumPreviewBehavior::DeferDeliveryPlans),
);
- let request = delivery_request(vec![reticulum_target("reticulum:preview-unavailable")]);
+ let request = delivery_request(vec![reticulum_target(
+ RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI,
+ )]);
let receipt = transport.deliver(request).expect("delivery receipt");
assert_eq!(receipt.target_receipts.len(), 1);
@@ -226,7 +257,10 @@ fn fetch_reports_preview_unavailable_without_observed_events() {
.expect("fetch receipt");
assert_eq!(receipt.request_id, "fetch-1");
- assert_eq!(receipt.endpoint_uri, "reticulum:preview-unavailable");
+ assert_eq!(
+ receipt.endpoint_uri,
+ RADROOTS_RETICULUM_PREVIEW_ENDPOINT_URI
+ );
assert_eq!(receipt.observed_event_count, 0);
assert_eq!(
receipt.implementation_state,