redfish 0.3.2

Production-grade Rust SDK for DMTF Redfish (async-first, optional blocking).
Documentation
#![cfg(feature = "_blocking")]

use std::time::Duration;

use http::StatusCode;
use redfish::{Auth, BlockingClient, RetryPolicy};
use tokio::runtime::Runtime;
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};

#[test]
fn blocking_service_root_get_works() {
    let rt = Runtime::new().unwrap();
    let server = rt.block_on(MockServer::start());

    rt.block_on(async {
        Mock::given(method("GET"))
            .and(path("/redfish/v1"))
            .respond_with(
                ResponseTemplate::new(200)
                    .set_body_raw(r#"{"RedfishVersion":"1.9.0"}"#, "application/json"),
            )
            .mount(&server)
            .await;
    });

    let client = BlockingClient::builder(&server.uri())
        .unwrap()
        .auth(Auth::none())
        .build()
        .unwrap();

    let root = client.service_root().get().unwrap();
    assert_eq!(root.redfish_version, "1.9.0");
}

#[test]
fn blocking_retries_on_503_for_idempotent_methods() {
    let rt = Runtime::new().unwrap();
    let server = rt.block_on(MockServer::start());

    rt.block_on(async {
        Mock::given(method("GET"))
            .and(path("/redfish/v1/Systems"))
            .respond_with(ResponseTemplate::new(503))
            .up_to_n_times(1)
            .expect(1)
            .mount(&server)
            .await;

        Mock::given(method("GET"))
            .and(path("/redfish/v1/Systems"))
            .respond_with(ResponseTemplate::new(200).set_body_raw(
                r#"{"Members":[{"@odata.id":"/redfish/v1/Systems/1"}],"Members@odata.count":1}"#,
                "application/json",
            ))
            .expect(1)
            .mount(&server)
            .await;
    });

    let retry = RetryPolicy::default()
        .with_max_retries(2)
        .with_base_delay(Duration::from_millis(1))
        .with_max_delay(Duration::from_millis(1))
        .with_jitter(false);

    let client = BlockingClient::builder(&server.uri())
        .unwrap()
        .retry_policy(retry)
        .build()
        .unwrap();

    let systems = client.systems().list().unwrap();
    assert_eq!(systems.members.len(), 1);
}

#[test]
fn blocking_does_not_retry_on_500() {
    let rt = Runtime::new().unwrap();
    let server = rt.block_on(MockServer::start());

    rt.block_on(async {
        Mock::given(method("GET"))
            .and(path("/redfish/v1/Systems"))
            .respond_with(ResponseTemplate::new(500))
            .expect(1)
            .mount(&server)
            .await;
    });

    let retry = RetryPolicy::default()
        .with_max_retries(2)
        .with_base_delay(Duration::from_millis(1))
        .with_max_delay(Duration::from_millis(1))
        .with_jitter(false);

    let client = BlockingClient::builder(&server.uri())
        .unwrap()
        .retry_policy(retry)
        .build()
        .unwrap();

    let err = client.systems().list().expect_err("expected 500 error");
    assert_eq!(err.status(), Some(StatusCode::INTERNAL_SERVER_ERROR));

    let requests = rt
        .block_on(server.received_requests())
        .expect("request history");
    assert_eq!(requests.len(), 1);
}

#[test]
fn blocking_session_create_parses_token_and_location() {
    let rt = Runtime::new().unwrap();
    let server = rt.block_on(MockServer::start());

    rt.block_on(async {
        Mock::given(method("POST"))
            .and(path("/redfish/v1/SessionService/Sessions"))
            .respond_with(
                ResponseTemplate::new(StatusCode::CREATED.as_u16())
                    .insert_header("X-Auth-Token", "abc123")
                    .insert_header("Location", "/redfish/v1/SessionService/Sessions/1")
                    .set_body_raw(
                        r#"{"Id":"1","Name":"Session","UserName":"admin"}"#,
                        "application/json",
                    ),
            )
            .mount(&server)
            .await;
    });

    let client = BlockingClient::builder(&server.uri())
        .unwrap()
        .build()
        .unwrap();
    let login = client.sessions().create("admin", "pw").unwrap();

    let _auth = redfish::Auth::SessionToken(login.token.clone());
    assert_eq!(login.location, "/redfish/v1/SessionService/Sessions/1");
    assert_eq!(login.session.unwrap().id.as_deref(), Some("1"));
}