unb-core 2.0.3

Core unb protocol types: envelope, session, routing, taxonomy
Documentation
use bytes::Bytes;
use unb_core::{ApplicationHead, Envelope, TargetPath};

#[test]
fn target_path_separates_final_node_from_local_subject() {
    let path = TargetPath::parse_application("/node-a/weather/current").unwrap();

    assert_eq!(path.target(), "node-a");
    assert_eq!(path.subject(), "weather.current");
    assert_eq!(path.to_string(), "/node-a/weather/current");
}

#[test]
fn discovery_path_contains_only_the_final_node() {
    let path = TargetPath::parse_discovery("/node.a").unwrap();

    assert_eq!(path.target(), "node.a");
    assert_eq!(path.subject(), "");
    assert_eq!(path.to_string(), "/node.a");
}

#[test]
fn malformed_target_paths_are_rejected_before_routing() {
    for invalid in [
        "weather/current",
        "/weather",
        "//weather",
        "/node-a/",
        "/node-a/weather//current",
        "/node-a/weather?unit=c",
        "/node-a/weather#current",
        "/node-a/we\nather",
        "/node a/weather",
        "/nøde/weather",
        "/n%20ode/weather",
        "/az/weather",
        "/node-a/weather..current",
    ] {
        assert!(
            TargetPath::parse_application(invalid).is_err(),
            "accepted malformed application path {invalid:?}"
        );
    }

    for invalid in [
        "node-a",
        "/",
        "/node-a/weather",
        "/node-a?detail=full",
        "/node-a#catalog",
        "/node\na",
        "/node a",
        "/nøde",
        "/n%20ode",
        "/az",
    ] {
        assert!(
            TargetPath::parse_discovery(invalid).is_err(),
            "accepted malformed discovery path {invalid:?}"
        );
    }
}

#[test]
fn semantic_parts_format_as_one_canonical_slash_path() {
    let application = TargetPath::application("node-a", "weather.current").unwrap();
    let discovery = TargetPath::discovery("node-a").unwrap();

    assert_eq!(application.to_string(), "/node-a/weather/current");
    assert_eq!(discovery.to_string(), "/node-a");
    assert!(TargetPath::application("node-a", "weather..current").is_err());
    assert!(TargetPath::discovery("node/a").is_err());
}

#[test]
fn application_heads_carry_target_and_local_subject_separately() {
    let request = http::Request::post("/node-a/weather/current")
        .body(Bytes::new())
        .unwrap();
    let envelope = Envelope::from_request(request).unwrap();

    assert_eq!(envelope.target, "node-a");
    assert_eq!(envelope.subject, "weather.current");

    let head = ApplicationHead::from_envelope(&envelope).unwrap();
    assert_eq!(head.target, "node-a");
    assert_eq!(head.subject, "weather.current");

    let projected = head.into_envelope();
    assert_eq!(projected.target, "node-a");
    assert_eq!(projected.subject, "weather.current");
}

#[test]
fn terminal_request_projection_hides_the_target_prefix() {
    let request = http::Request::post("/node-a/weather/current")
        .body(Bytes::new())
        .unwrap();
    let envelope = Envelope::from_request(request).unwrap();

    let local = envelope.to_local_request().unwrap();
    assert_eq!(local.uri().path(), "/weather/current");
}