motorcortex-rust 0.5.0

Motorcortex Rust: a Rust client for the Motorcortex Core real-time control system (async + blocking).
Documentation
//! End-to-end smoke test for the new `core::Request` handle against
//! the vendored `test_server`. Proves the actor pattern's connect /
//! disconnect cycle is wired through correctly.

use std::time::Duration;

use motorcortex_rust::ConnectionOptions;
use motorcortex_rust::core::{ConnectionState, Request};

use crate::{CERT_PATH, URL_REQ};

#[tokio::test]
async fn test_async_request_drop_without_disconnect_does_not_hang() {
    // Sibling to the Subscribe drop-order test: ensure the Request
    // driver exits cleanly when the caller drops the handle without
    // calling `disconnect()` first. The driver has a token-refresh
    // helper thread to join on shutdown; this confirms the join
    // terminates inside the tick window.
    let opts = ConnectionOptions::new(CERT_PATH.to_string(), 5000, 5000)
        .with_token_refresh_interval(Duration::from_millis(200));
    let req = Request::connect_to(URL_REQ, opts).await.expect("connect");
    tokio::time::sleep(Duration::from_millis(300)).await;
    tokio::time::timeout(Duration::from_secs(3), async move {
        tokio::task::spawn_blocking(move || {
            drop(req);
        })
        .await
        .unwrap();
    })
    .await
    .expect("Request drop without disconnect must not hang");
}

#[tokio::test]
async fn test_async_request_round_trips_connect_and_disconnect() {
    let req = Request::new();

    // Initial state is Disconnected.
    assert_eq!(*req.state().borrow(), ConnectionState::Disconnected);

    let opts = ConnectionOptions::new(CERT_PATH.to_string(), 5000, 5000);
    req.connect(URL_REQ, opts)
        .await
        .expect("connect must succeed against the vendored server");
    assert_eq!(*req.state().borrow(), ConnectionState::Connected);

    req.disconnect()
        .await
        .expect("disconnect must succeed after a successful connect");
    assert_eq!(*req.state().borrow(), ConnectionState::Disconnected);
}

#[tokio::test]
async fn test_connection_state_watch_observes_every_transition() {
    // Gap-check for Phase 5: the watch channel must surface every
    // transition the driver goes through, in order. This test walks
    // the common happy-path cycle (Disconnected → Connected →
    // Disconnected → Connected → Disconnected) and asserts the
    // observer sees each value in sequence. Drop-event transitions
    // (ConnectionLost / SessionExpired / Disconnected on
    // reconnect=false) are each covered end-to-end by
    // tests/integration/async_reconnect.rs.
    let req = Request::new();
    let mut state = req.state();
    assert_eq!(*state.borrow_and_update(), ConnectionState::Disconnected);

    let opts = ConnectionOptions::new(CERT_PATH.to_string(), 5000, 5000);

    // Disconnected → Connected.
    req.connect(URL_REQ, opts.clone()).await.expect("connect");
    state.changed().await.expect("state must update on connect");
    assert_eq!(*state.borrow_and_update(), ConnectionState::Connected);

    // Connected → Disconnected.
    req.disconnect().await.expect("disconnect");
    state
        .changed()
        .await
        .expect("state must update on disconnect");
    assert_eq!(*state.borrow_and_update(), ConnectionState::Disconnected);

    // And a second cycle — reconnecting after an explicit disconnect
    // works the same way.
    req.connect(URL_REQ, opts).await.expect("reconnect");
    state.changed().await.expect("state must update on reconnect");
    assert_eq!(*state.borrow_and_update(), ConnectionState::Connected);

    req.disconnect().await.expect("final disconnect");
    state
        .changed()
        .await
        .expect("state must update on final disconnect");
    assert_eq!(*state.borrow_and_update(), ConnectionState::Disconnected);
}

#[tokio::test]
async fn test_async_request_clone_shares_driver() {
    // Cloning the handle must hand out handles backed by the same
    // driver — both observe the same connect.
    let req = Request::new();
    let clone = req.clone();
    let opts = ConnectionOptions::new(CERT_PATH.to_string(), 5000, 5000);
    req.connect(URL_REQ, opts).await.expect("connect");
    assert_eq!(*clone.state().borrow(), ConnectionState::Connected);
    clone
        .disconnect()
        .await
        .expect("disconnect from the clone must affect the shared driver");
    assert_eq!(*req.state().borrow(), ConnectionState::Disconnected);
}