cloud-tasks-emulator 0.1.1

Local emulator of the Google Cloud Tasks v2 API
Documentation
mod support;

use cloud_tasks_emulator::proto::google::cloud::tasks::v2::cloud_tasks_client::CloudTasksClient;
use cloud_tasks_emulator::proto::google::cloud::tasks::v2::{
    AppEngineRouting, CreateQueueRequest, GetQueueRequest, ListQueuesRequest, Queue,
};
use tokio::net::TcpListener;
use tonic::Code;

fn create(parent: &str, queue: Queue) -> CreateQueueRequest {
    CreateQueueRequest {
        parent: parent.to_owned(),
        queue: Some(queue),
    }
}

fn named(name: &str) -> Queue {
    Queue {
        name: name.to_owned(),
        ..Default::default()
    }
}

#[tokio::test]
async fn get_queue_returns_created_queue() {
    let mut client = support::start_emulator().await;
    let created = client
        .create_queue(create(
            "projects/p/locations/l",
            named("projects/p/locations/l/queues/q"),
        ))
        .await
        .unwrap()
        .into_inner();

    let queue = client
        .get_queue(GetQueueRequest {
            name: "projects/p/locations/l/queues/q".to_owned(),
        })
        .await
        .unwrap()
        .into_inner();

    assert_eq!(queue, created);
}

#[tokio::test]
async fn list_queues_returns_location_queues_in_name_order() {
    let mut client = support::start_emulator().await;
    client
        .create_queue(create(
            "projects/p/locations/l",
            named("projects/p/locations/l/queues/b"),
        ))
        .await
        .unwrap();
    client
        .create_queue(create(
            "projects/p/locations/l",
            named("projects/p/locations/l/queues/a"),
        ))
        .await
        .unwrap();
    client
        .create_queue(create(
            "projects/p/locations/other",
            named("projects/p/locations/other/queues/c"),
        ))
        .await
        .unwrap();

    let response = client
        .list_queues(ListQueuesRequest {
            parent: "projects/p/locations/l".to_owned(),
            ..Default::default()
        })
        .await
        .unwrap()
        .into_inner();

    assert_eq!(
        response
            .queues
            .iter()
            .map(|queue| queue.name.as_str())
            .collect::<Vec<_>>(),
        [
            "projects/p/locations/l/queues/a",
            "projects/p/locations/l/queues/b"
        ]
    );
}

#[tokio::test]
async fn list_queues_pages_by_page_size() {
    let mut client = support::start_emulator().await;
    client
        .create_queue(create(
            "projects/p/locations/l",
            named("projects/p/locations/l/queues/a"),
        ))
        .await
        .unwrap();
    client
        .create_queue(create(
            "projects/p/locations/l",
            named("projects/p/locations/l/queues/b"),
        ))
        .await
        .unwrap();
    client
        .create_queue(create(
            "projects/p/locations/l",
            named("projects/p/locations/l/queues/c"),
        ))
        .await
        .unwrap();
    let first = client
        .list_queues(ListQueuesRequest {
            parent: "projects/p/locations/l".to_owned(),
            page_size: 2,
            ..Default::default()
        })
        .await
        .unwrap()
        .into_inner();

    let second = client
        .list_queues(ListQueuesRequest {
            parent: "projects/p/locations/l".to_owned(),
            page_size: 2,
            page_token: first.next_page_token,
            ..Default::default()
        })
        .await
        .unwrap()
        .into_inner();

    assert_eq!(
        (
            second
                .queues
                .iter()
                .map(|queue| queue.name.as_str())
                .collect::<Vec<_>>(),
            second.next_page_token.is_empty()
        ),
        (vec!["projects/p/locations/l/queues/c"], false)
    );
}

#[tokio::test]
async fn list_queues_rejects_malformed_filter() {
    let mut client = support::start_emulator().await;

    let status = client
        .list_queues(ListQueuesRequest {
            parent: "projects/p/locations/l".to_owned(),
            filter: "state: (".to_owned(),
            ..Default::default()
        })
        .await
        .unwrap_err();

    assert_eq!(status.code(), Code::InvalidArgument);
}

#[tokio::test]
async fn create_queue_rejects_routing_override() {
    let mut client = support::start_emulator().await;

    let status = client
        .create_queue(create(
            "projects/p/locations/l",
            Queue {
                app_engine_routing_override: Some(AppEngineRouting {
                    service: "worker".to_owned(),
                    ..Default::default()
                }),
                ..named("projects/p/locations/l/queues/q")
            },
        ))
        .await
        .unwrap_err();

    assert_eq!(status.code(), Code::NotFound);
}

#[tokio::test]
async fn startup_creates_listed_queues() {
    let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
    let url = format!("http://{}", listener.local_addr().unwrap());
    tokio::spawn(cloud_tasks_emulator::serve(
        listener,
        url.clone(),
        vec![
            "projects/p/locations/l/queues/b".to_owned(),
            "projects/p/locations/l/queues/a".to_owned(),
        ],
    ));
    let mut client = CloudTasksClient::connect(url).await.unwrap();

    let response = client
        .list_queues(ListQueuesRequest {
            parent: "projects/p/locations/l".to_owned(),
            ..Default::default()
        })
        .await
        .unwrap()
        .into_inner();

    assert_eq!(
        response
            .queues
            .iter()
            .map(|queue| queue.name.as_str())
            .collect::<Vec<_>>(),
        [
            "projects/p/locations/l/queues/a",
            "projects/p/locations/l/queues/b"
        ]
    );
}

#[tokio::test]
async fn startup_fails_on_rejected_queue() {
    let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();

    let error = cloud_tasks_emulator::serve(
        listener,
        "http://127.0.0.1:8123".to_owned(),
        vec!["projects/p/locations/l/queues/a_b".to_owned()],
    )
    .await
    .unwrap_err();

    assert_eq!(
        error.to_string(),
        r#"Cannot create projects/p/locations/l/queues/a_b: Queue ID "a_b" can contain only letters ([A-Za-z]), numbers ([0-9]), or hyphens (-). Queue ID must be between 1 and 100 characters."#
    );
}