nextcloud-ocs 0.1.2

OCS API envelope and client for the Nextcloud Rust SDK
Documentation
use std::future::IntoFuture;

use nextcloud_sdk_core::ApiPath;

use super::{
    CAPABILITIES_OK, Capabilities, FakeTransport, OCS_FAILURE, block_on, capabilities_path, client,
};
use crate::query::{OcsFormat, Query};

#[test]
fn ok_response_yields_the_payload() {
    let transport = FakeTransport::new(200, CAPABILITIES_OK);
    let capabilities: Capabilities = block_on(
        client(transport.clone())
            .get(capabilities_path(), Query::new())
            .into_future(),
    )
    .expect("the envelope carries a payload");

    assert_eq!(capabilities.version.major, 32);
    assert_eq!(capabilities.version.string, "32.0.0");
    assert!(transport.request().is_some());
}

#[test]
fn request_carries_ocs_headers_and_credentials() {
    let transport = FakeTransport::new(200, CAPABILITIES_OK);
    let _: Capabilities = block_on(
        client(transport.clone())
            .get(capabilities_path(), Query::new())
            .into_future(),
    )
    .expect("payload decodes");

    let request = transport.request().expect("transport saw a request");
    assert_eq!(request.headers().get("Accept"), Some("application/json"));
    assert_eq!(request.headers().get("OCS-APIRequest"), Some("true"));
    assert_eq!(
        request.headers().get("Authorization"),
        Some("Bearer test-token")
    );
    assert_eq!(
        request.path().as_str(),
        "/ocs/v2.php/cloud/capabilities",
        "an empty query leaves no trailing separator"
    );
}

#[test]
fn query_format_reaches_the_request_path() {
    let transport = FakeTransport::new(200, CAPABILITIES_OK);
    let _: Capabilities = block_on(
        client(transport.clone())
            .get(capabilities_path(), Query::new().format(OcsFormat::Json))
            .into_future(),
    )
    .expect("payload decodes");

    assert_eq!(
        transport
            .request()
            .map(|request| request.path().as_str().to_owned()),
        Some("/ocs/v2.php/cloud/capabilities?format=json".to_owned())
    );
}

#[test]
fn failing_statuscode_becomes_a_status_error() {
    let transport = FakeTransport::new(200, OCS_FAILURE);
    let error = block_on(
        client(transport)
            .get::<Capabilities>(capabilities_path(), Query::new())
            .into_future(),
    )
    .expect_err("meta.statuscode 997 is a failure");

    assert!(
        error.as_status().is_some(),
        "the OCS verdict fails the call"
    );
    assert_eq!(error.status_code(), Some(200));
    assert!(
        error.body().unwrap_or_default().contains("997"),
        "the OCS body stays reachable so the code and message survive"
    );
}

#[test]
fn non_2xx_becomes_a_status_error() {
    let transport = FakeTransport::new(503, "");
    let error = block_on(
        client(transport)
            .get::<Capabilities>(capabilities_path(), Query::new())
            .into_future(),
    )
    .expect_err("503 is not acceptable");

    assert!(error.as_status().is_some());
    assert_eq!(error.status_code(), Some(503));
    assert!(error.is_retryable());
}

#[test]
fn undecodable_body_names_the_target_type() {
    let transport = FakeTransport::new(200, r#"{"not_ocs": true}"#);
    let error = block_on(
        client(transport)
            .get::<Capabilities>(capabilities_path(), Query::new())
            .into_future(),
    )
    .expect_err("a missing `ocs` wrapper cannot decode");

    let decode = error.as_decode().expect("a decode failure");
    assert!(decode.target().contains("OcsEnvelope"));
    assert!(!error.is_retryable());
}

#[test]
fn envelope_without_a_verdict_is_a_decode_error() {
    let transport = FakeTransport::new(200, r#"{"ocs":{"meta":{},"data":null}}"#);
    let error = block_on(
        client(transport)
            .get::<Capabilities>(capabilities_path(), Query::new())
            .into_future(),
    )
    .expect_err("no verdict is not a success");

    assert!(error.as_decode().is_some());
}

#[test]
fn non_ocs_paths_are_refused_before_sending() {
    let transport = FakeTransport::new(200, CAPABILITIES_OK);
    let error = block_on(
        client(transport.clone())
            .get::<Capabilities>(
                ApiPath::dav("files/user").expect("valid DAV path"),
                Query::new(),
            )
            .into_future(),
    )
    .expect_err("a DAV path is not an OCS call");

    assert!(error.as_url().is_some());
    assert!(transport.request().is_none(), "nothing was sent");
}