hubuum_client 0.0.2

Rust client library for the Hubuum API with synchronous and asynchronous interfaces.
Documentation
use std::str::FromStr;

use httpmock::prelude::*;
use serde_json::json;
use yare::parameterized;

use super::{ClientCore, UrlParams, r#async as async_client, sync as sync_client};
use crate::ApiError;
use crate::client::{Authenticated, Unauthenticated};
use crate::endpoints::Endpoint;
use crate::types::{BaseUrl, Credentials};

const USERNAME: &str = "tester";
const PASSWORD: &str = "secret";
const TOKEN: &str = "integration-token";

fn mock_login(server: &MockServer) {
    server.mock(|when, then| {
        when.method(POST)
            .path("/api/v0/auth/login")
            .json_body(json!({ "username": USERNAME, "password": PASSWORD }));
        then.status(200)
            .header("content-type", "application/json")
            .json_body(json!({ "token": TOKEN }));
    });
}

fn build_sync_client(server: &MockServer) -> Result<sync_client::Client<Authenticated>, ApiError> {
    let base_url = BaseUrl::from_str(&server.base_url()).expect("base URL should be valid");
    sync_client::Client::new_with_certificate_validation(base_url, true)
        .login(Credentials::new(USERNAME.to_string(), PASSWORD.to_string()))
}

async fn build_async_client(
    server: &MockServer,
) -> Result<async_client::Client<Authenticated>, ApiError> {
    let base_url = BaseUrl::from_str(&server.base_url()).expect("base URL should be valid");
    async_client::Client::new_with_certificate_validation(base_url, true)
        .login(Credentials::new(USERNAME.to_string(), PASSWORD.to_string()))
        .await
}

#[parameterized(
    login_foo = { "https://foo.bar.com", Endpoint::Login },
    users_foo = { "https://foo.bar.com", Endpoint::Users },
    classes_foo = { "https://foo.bar.com", Endpoint::Classes },
    login_bar = { "https://bar.baz.com", Endpoint::Login },
    users_bar = { "https://bar.baz.com", Endpoint::Users },
    classes_bar = { "https://bar.baz.com", Endpoint::Classes }
)]
fn sync_build_url_matches_endpoint(server: &str, endpoint: Endpoint) {
    let base_url = BaseUrl::from_str(server).unwrap();
    let client = sync_client::Client::<Unauthenticated>::new_without_certificate_validation(
        base_url.clone(),
    );

    assert_eq!(
        client.build_url(&endpoint, UrlParams::default()),
        format!(
            "{}{}",
            base_url.with_trailing_slash(),
            endpoint.trim_start_matches('/')
        )
    );
}

#[parameterized(
    login_foo = { "https://foo.bar.com", Endpoint::Login },
    users_foo = { "https://foo.bar.com", Endpoint::Users },
    classes_foo = { "https://foo.bar.com", Endpoint::Classes },
    login_bar = { "https://bar.baz.com", Endpoint::Login },
    users_bar = { "https://bar.baz.com", Endpoint::Users },
    classes_bar = { "https://bar.baz.com", Endpoint::Classes }
)]
fn async_build_url_matches_endpoint(server: &str, endpoint: Endpoint) {
    let base_url = BaseUrl::from_str(server).unwrap();
    let client = async_client::Client::<Unauthenticated>::new_without_certificate_validation(
        base_url.clone(),
    );

    assert_eq!(
        client.build_url(&endpoint, UrlParams::default()),
        format!(
            "{}{}",
            base_url.with_trailing_slash(),
            endpoint.trim_start_matches('/')
        )
    );
}

#[test]
fn sync_request_patch_requires_patch_id() {
    let server = MockServer::start();
    mock_login(&server);
    let client = build_sync_client(&server).expect("login should succeed");

    let err = client
        .request_with_endpoint::<sync_client::EmptyPostParams, serde_json::Value>(
            reqwest::Method::PATCH,
            &Endpoint::Classes,
            vec![],
            vec![],
            sync_client::EmptyPostParams {},
        )
        .expect_err("PATCH without patch_id should fail");

    assert!(matches!(err, ApiError::MissingUrlIdentifier));
}

#[tokio::test]
async fn async_request_patch_requires_patch_id() {
    let server = MockServer::start();
    mock_login(&server);
    let client = build_async_client(&server)
        .await
        .expect("login should succeed");

    let err = client
        .request_with_endpoint::<async_client::EmptyPostParams, serde_json::Value>(
            reqwest::Method::PATCH,
            &Endpoint::Classes,
            vec![],
            vec![],
            async_client::EmptyPostParams {},
        )
        .await
        .expect_err("PATCH without patch_id should fail");

    assert!(matches!(err, ApiError::MissingUrlIdentifier));
}