lxmf-sdk 0.7.0

High-level Rust SDK for LXMF clients and RPC-backed LXMF workflows.
Documentation
#[test]
fn execute_envelope_routes_marker_operations_locally() {
    let app = Client::new(MockBackend::new());

    let created = app
        .command(
            "sdk_marker_create_v2",
            serde_json::json!({
                "label": "Alpha",
                "position": { "lat": 35.0, "lon": -115.0, "alt_m": 1200.0 },
                "topic_id": "topic-1",
            }),
        )
        .expect("marker create");
    assert_eq!(created.operation_id.as_str(), "app.marker.create");
    assert_eq!(created.payload["marker_id"], json!("marker-1"));

    let listed = app
        .query(
            "app.marker.list",
            serde_json::json!({
                "topic_id": "topic-1",
                "limit": 10,
            }),
        )
        .expect("marker list");
    assert_eq!(listed.payload["markers"].as_array().expect("marker rows").len(), 1);

    let updated = app
        .command(
            "sdk_marker_update_position_v2",
            serde_json::json!({
                "marker_id": "marker-1",
                "expected_revision": 2,
                "position": { "lat": 36.0, "lon": -116.0, "alt_m": null },
            }),
        )
        .expect("marker update");
    assert_eq!(updated.operation_id.as_str(), "app.marker.update_position");
    assert_eq!(updated.payload["revision"], json!(3));

    let deleted = app
        .command(
            "app.marker.delete",
            serde_json::json!({
                "marker_id": "marker-1",
                "expected_revision": 3,
            }),
        )
        .expect("marker delete");
    assert_eq!(deleted.operation_id.as_str(), "app.marker.delete");
    assert_eq!(deleted.payload["accepted"], json!(true));
}

#[test]
fn execute_envelope_routes_runtime_start_and_stop_locally() {
    let app = Client::new(MockBackend::new());
    let start = app
        .command(
            "app.runtime.start",
            serde_json::to_value(Config::testing_default()).expect("config value"),
        )
        .expect("runtime start");
    assert_eq!(start.operation_id.as_str(), "app.runtime.start");
    assert_eq!(
        start.payload.get("profile").and_then(|value| value.as_str()),
        Some("testing_default")
    );

    let stop = app
        .command("app.runtime.stop", serde_json::json!({ "mode": "graceful" }))
        .expect("runtime stop");
    assert_eq!(stop.operation_id.as_str(), "app.runtime.stop");
    assert_eq!(stop.payload.get("accepted").and_then(|value| value.as_bool()), Some(true));
}

#[test]
fn execute_envelope_routes_delivery_send_locally() {
    let backend = MockBackend::new();
    backend.queue_send_result(Ok(crate::MessageId("msg-1".to_owned())));
    let app = Client::new(backend);
    app.start(Config::testing_default()).expect("start");

    let response = app
        .command(
            "app.delivery.send",
            serde_json::json!({
                "source": "src",
                "destination": "dst",
                "payload": { "content": "hello" },
                "correlation_id": "corr-1"
            }),
        )
        .expect("delivery send");
    assert_eq!(response.operation_id.as_str(), "app.delivery.send");
    assert_eq!(response.payload.get("message_id").and_then(|value| value.as_str()), Some("msg-1"));
}

#[test]
fn execute_envelope_routes_custom_commands_via_remote_command_backend() {
    let backend = MockBackend::new();
    backend.queue_remote_command_result(Ok(crate::domain::RemoteCommandResponse {
        accepted: true,
        payload: serde_json::json!({
            "command_id": "cmdreq-1",
            "correlation_id": "cmd-1",
            "command": "vendor.example.custom",
            "target": null,
            "command_state": "dispatched",
        }),
        extensions: BTreeMap::from([("transport".to_owned(), serde_json::json!("remote"))]),
    }));
    let app = Client::new(backend);
    app.start(Config::desktop_default().with_custom_operation(OperationEntry::new(
        "vendor.example.custom",
        "custom",
        OperationKind::Command,
        TransportVariant::Extension,
        "Custom vendor command.",
    )))
    .expect("start");
    let response = app
        .command("vendor.example.custom", serde_json::json!({ "value": 1 }))
        .expect("custom command");
    assert_eq!(response.operation_id.as_str(), "vendor.example.custom");
    assert_eq!(
        response.payload.get("command_state").and_then(|value| value.as_str()),
        Some("dispatched")
    );
    assert_eq!(
        response.extensions.get("transport").and_then(|value| value.as_str()),
        Some("remote")
    );
}

#[test]
fn execute_envelope_rejects_kind_mismatches() {
    let app = Client::new(MockBackend::new());
    let err = app
        .execute_envelope(Envelope::command("app.identity.list", serde_json::json!({})))
        .expect_err("kind mismatch should fail");
    assert_eq!(err.code.as_str(), "SDK_APP_VALIDATION_INVALID_ARGUMENT");
}

#[test]
fn execute_envelope_routes_unhandled_queries_to_backend_envelope_path() {
    let backend = MockBackend::new();
    backend.queue_envelope_result(Ok(crate::app::EnvelopeResponse {
        operation_id: crate::app::OperationId::from("app.message.history.list"),
        kind: crate::app::EnvelopeKind::Result,
        accepted: true,
        correlation_id: Some("corr-1".to_owned()),
        payload: serde_json::json!({ "messages": [] }),
        extensions: BTreeMap::from([("via".to_owned(), serde_json::json!("envelope"))]),
    }));
    let app = Client::new(backend);
    let response = app
        .query("app.message.history.list", serde_json::json!({ "limit": 10 }))
        .expect("history query");
    assert_eq!(response.operation_id.as_str(), "app.message.history.list");
    assert_eq!(response.extensions.get("via").and_then(|value| value.as_str()), Some("envelope"));
}

#[test]
fn execute_envelope_routes_voice_operations_locally() {
    let backend = MockBackend::new();
    backend.queue_voice_open_result(Ok(crate::domain::VoiceSessionId("voice-9".to_owned())));
    backend.queue_voice_update_result(Ok(crate::domain::VoiceSessionState::Active));
    backend.queue_voice_close_result(Ok(Ack { accepted: true, revision: None }));
    let app = Client::new(backend);

    let opened = app
        .command(
            "app.voice.session.open",
            serde_json::json!({ "peer_id": "node-b", "codec_hint": "opus" }),
        )
        .expect("voice open");
    assert_eq!(opened.operation_id.as_str(), "app.voice.session.open");
    assert_eq!(
        serde_json::from_value::<crate::domain::VoiceSessionId>(opened.payload).expect("voice id"),
        crate::domain::VoiceSessionId("voice-9".to_owned())
    );

    let updated = app
        .command(
            "app.voice.session.update",
            serde_json::json!({ "session_id": "voice-9", "state": "active" }),
        )
        .expect("voice update");
    assert_eq!(updated.operation_id.as_str(), "app.voice.session.update");
    assert_eq!(
        serde_json::from_value::<crate::domain::VoiceSessionState>(updated.payload)
            .expect("voice state"),
        crate::domain::VoiceSessionState::Active
    );

    let closed =
        app.command("app.voice.session.close", serde_json::json!("voice-9")).expect("voice close");
    assert_eq!(closed.operation_id.as_str(), "app.voice.session.close");
    assert_eq!(closed.payload.get("accepted").and_then(|value| value.as_bool()), Some(true));
    assert_eq!(closed.payload.get("session_id").and_then(|value| value.as_str()), Some("voice-9"));
}