tus_client 0.1.1

A Rust native client library to interact with *tus* enabled endpoints.
Documentation
use std::collections::HashMap;
use std::io::Write;
use tempfile::NamedTempFile;
use tus_client;
use tus_client::http::{HttpHandler, HttpMethod, HttpRequest, HttpResponse};
use tus_client::{Error, TusExtension};

struct TestHandler {
    pub upload_progress: usize,
    pub total_upload_size: usize,
    pub status_code: usize,
    pub tus_version: String,
    pub extensions: String,
    pub max_upload_size: usize,
}

impl Default for TestHandler {
    fn default() -> Self {
        TestHandler {
            upload_progress: 1234,
            total_upload_size: 2345,
            status_code: 200,
            tus_version: String::from("1.0.0"),
            extensions: String::from(""),
            max_upload_size: 12345,
        }
    }
}

impl HttpHandler for TestHandler {
    fn handle_request(&self, req: HttpRequest) -> Result<HttpResponse, Error> {
        match &req.method {
            HttpMethod::Head => {
                let mut headers = HashMap::new();
                headers.insert(
                    "upload-length".to_owned(),
                    self.total_upload_size.to_string(),
                );
                headers.insert("upload-offset".to_owned(), self.upload_progress.to_string());
                headers.insert(
                    "upload-metadata".to_owned(),
                    base64::encode("key_one:value_one;key_two:value_two;k"),
                );

                Ok(HttpResponse {
                    status_code: self.status_code,
                    headers,
                })
            }
            HttpMethod::Options => {
                let mut headers = HashMap::new();
                headers.insert("tus-version".to_owned(), self.tus_version.clone());
                headers.insert("tus-extension".to_owned(), self.extensions.clone());
                headers.insert("tus-max-size".to_owned(), self.max_upload_size.to_string());

                Ok(HttpResponse {
                    status_code: self.status_code,
                    headers,
                })
            }
            HttpMethod::Patch => {
                let mut headers = HashMap::new();
                headers.insert("tus-version".to_owned(), self.tus_version.clone());
                headers.insert(
                    "upload-offset".to_owned(),
                    (req.body.unwrap().len()
                        + req
                            .headers
                            .get("upload-offset")
                            .unwrap()
                            .parse::<usize>()
                            .unwrap())
                    .to_string(),
                );

                Ok(HttpResponse {
                    status_code: self.status_code,
                    headers,
                })
            }
            HttpMethod::Post => {
                let mut headers = HashMap::new();
                headers.insert("tus-version".to_owned(), self.tus_version.clone());
                headers.insert("location".to_owned(), "/something_else".to_owned());

                Ok(HttpResponse {
                    status_code: self.status_code,
                    headers,
                })
            }
            HttpMethod::Delete => {
                let mut headers = HashMap::new();
                headers.insert("tus-version".to_owned(), self.tus_version.clone());

                Ok(HttpResponse {
                    status_code: self.status_code,
                    headers,
                })
            }
            _ => unreachable!(),
        }
    }
}

fn create_temp_file() -> NamedTempFile {
    let mut temp_file = NamedTempFile::new().unwrap();
    let buffer: Vec<u8> = (0..(1024 * 763)).map(|_| rand::random::<u8>()).collect();
    for _ in 0..20 {
        temp_file.write_all(&buffer[..]).unwrap();
    }
    temp_file
}

#[test]
fn should_report_correct_upload_progress() {
    let client = tus_client::Client::new(TestHandler {
        status_code: 204,
        ..TestHandler::default()
    });

    let info = client
        .get_info("/something")
        .expect("'get_progress' call failed");

    let metadata = info.metadata.unwrap();
    assert_eq!(1234, info.bytes_uploaded);
    assert_eq!(2345, info.total_size.unwrap());
    assert_eq!(
        String::from("value_one"),
        metadata.get("key_one").unwrap().to_owned()
    );
    assert_eq!(
        String::from("value_two"),
        metadata.get("key_two").unwrap().to_owned()
    );
}

#[test]
fn should_return_not_found_at_4xx_status() {
    let client = tus_client::Client::new(TestHandler {
        status_code: 400,
        ..TestHandler::default()
    });

    let result = client.get_info("/something");

    assert!(result.is_err());
    match result {
        Err(tus_client::Error::NotFoundError) => {}
        _ => panic!("Expected 'Error::NotFoundError'"),
    }
}

#[test]
fn should_return_server_info() {
    let client = tus_client::Client::new(TestHandler {
        status_code: 204,
        tus_version: String::from("1.0.0,0.2.2"),
        extensions: String::from("creation, termination"),
        ..TestHandler::default()
    });

    let result = client
        .get_server_info("/something")
        .expect("'get_server_info' call failed");

    assert_eq!(vec!["1.0.0", "0.2.2"], result.supported_versions);
    assert_eq!(
        vec![TusExtension::Creation, TusExtension::Termination],
        result.extensions
    );
    assert_eq!(12345, result.max_upload_size.unwrap());
}

#[test]
fn should_upload_file() {
    let temp_file = create_temp_file();

    let client = tus_client::Client::new(TestHandler {
        upload_progress: 0,
        total_upload_size: temp_file.as_file().metadata().unwrap().len() as usize,
        status_code: 204,
        ..TestHandler::default()
    });

    client
        .upload("/something", temp_file.path())
        .expect("'upload' call failed");
}

#[test]
fn should_upload_file_with_custom_chunk_size() {
    let temp_file = create_temp_file();

    let client = tus_client::Client::new(TestHandler {
        upload_progress: 0,
        total_upload_size: temp_file.as_file().metadata().unwrap().len() as usize,
        status_code: 204,
        ..TestHandler::default()
    });

    client
        .upload_with_chunk_size("/something", temp_file.path(), 9 * 87 * 65 * 43)
        .expect("'upload_with_chunk_size' call failed");
}

#[test]
fn should_receive_upload_path() {
    let temp_file = create_temp_file();

    let client = tus_client::Client::new(TestHandler {
        status_code: 201,
        ..TestHandler::default()
    });

    let mut metadata = HashMap::new();
    metadata.insert("key_one".to_owned(), "value_one".to_owned());
    metadata.insert("key_two".to_owned(), "value_two".to_owned());

    let result = client
        .create("/something", temp_file.path())
        .expect("'create_with_metadata' call failed");

    assert!(!result.is_empty());
}

#[test]
fn should_receive_upload_path_with_metadata() {
    let temp_file = create_temp_file();

    let client = tus_client::Client::new(TestHandler {
        status_code: 201,
        ..TestHandler::default()
    });

    let mut metadata = HashMap::new();
    metadata.insert("key_one".to_owned(), "value_one".to_owned());
    metadata.insert("key_two".to_owned(), "value_two".to_owned());

    let result = client
        .create_with_metadata("/something", temp_file.path(), metadata)
        .expect("'create_with_metadata' call failed");

    assert!(!result.is_empty());
}

#[test]
fn should_receive_204_after_deleting_file() {
    let client = tus_client::Client::new(TestHandler {
        status_code: 204,
        ..TestHandler::default()
    });

    client.delete("/something").expect("'delete' call failed");
}