pact-broker-cli 0.7.0

A Rust and CLI client for the Pact Broker. Publish and retrieve pacts and verification results.
Documentation
use maplit::hashmap;

use crate::cli::pact_broker::main::{
    HALClient, PactBrokerError,
    utils::{
        follow_templated_broker_relation, get_auth, get_broker_relation, get_broker_url,
        get_custom_headers, get_ssl_options,
    },
};

pub fn delete_webhook(args: &clap::ArgMatches) -> Result<String, PactBrokerError> {
    let broker_url = get_broker_url(args).trim_end_matches('/').to_string();
    let auth = get_auth(args);
    let custom_headers = get_custom_headers(args);
    let ssl_options = get_ssl_options(args);

    let webhook_uuid = args
        .try_get_one::<String>("uuid")
        .unwrap()
        .ok_or_else(|| PactBrokerError::IoError("Webhook UUID is required".to_string()))?;

    let res = tokio::runtime::Runtime::new().unwrap().block_on(async {
        let hal_client: HALClient = HALClient::with_url(
            &broker_url,
            Some(auth.clone()),
            ssl_options.clone(),
            custom_headers.clone(),
        );

        // Get the pb:webhook relation from the index
        let pb_webhook_href_path = get_broker_relation(
            hal_client.clone(),
            "pb:webhook".to_string(),
            broker_url.to_string(),
        )
        .await?;

        // First, get the webhook to check if it exists
        let template_values = hashmap! { "uuid".to_string() => webhook_uuid.to_string() };

        // Try to fetch the webhook first
        match follow_templated_broker_relation(
            hal_client.clone(),
            "pb:webhook".to_string(),
            pb_webhook_href_path.clone(),
            template_values.clone(),
        )
        .await
        {
            Ok(_webhook_data) => {
                // Webhook exists, now delete it
                let link = crate::cli::pact_broker::main::Link {
                    name: "pb:webhook".to_string(),
                    href: Some(pb_webhook_href_path),
                    templated: true,
                    title: None,
                };

                hal_client.delete_url(&link, &template_values).await
            }
            Err(err) => Err(err),
        }
    });

    match res {
        Ok(_) => {
            let message = format!("Webhook with UUID {} successfully deleted", webhook_uuid);
            println!("{}", message);
            Ok(message)
        }
        Err(PactBrokerError::NotFound(_)) => {
            let message = format!("Webhook with UUID {} was not found", webhook_uuid);
            println!("{}", message);
            Ok(message)
        }
        Err(err) => Err(err),
    }
}

#[cfg(test)]
mod delete_webhook_tests {
    use super::delete_webhook;
    use crate::cli::pact_broker::main::subcommands::add_delete_webhook_subcommand;
    use pact_consumer::builders::InteractionBuilder;
    use pact_consumer::prelude::*;
    use pact_models::PactSpecification;
    use serde_json::json;

    fn setup_mock_server(interactions: Vec<InteractionBuilder>) -> Box<dyn ValidatingMockServer> {
        let config = MockServerConfig {
            pact_specification: PactSpecification::V2,
            ..MockServerConfig::default()
        };
        let mut pact_builder = PactBuilder::new("pact-broker-cli", "Pact Broker");
        for i in interactions {
            pact_builder.push_interaction(&i.build());
        }
        pact_builder.start_mock_server(None, Some(config))
    }

    fn base_args(mock_server_url: &str, uuid: &str) -> Vec<String> {
        vec![
            "delete-webhook".to_string(),
            "-b".to_string(),
            mock_server_url.to_string(),
            "--uuid".to_string(),
            uuid.to_string(),
        ]
    }

    fn index_interaction() -> impl Fn(InteractionBuilder) -> InteractionBuilder {
        |mut i: InteractionBuilder| {
            i.request
                .get()
                .path("/")
                .header("Accept", "application/hal+json")
                .header("Accept", "application/json");
            i.response
                .status(200)
                .header("Content-Type", "application/hal+json;charset=utf-8")
                .json_body(json_pattern!({
                "_links": {
                    "pb:webhook": {
                    "href": term!("http:\\/\\/.*\\/webhooks\\/.*", "http://localhost/webhooks/{uuid}")
                    }
                }
                }));
            i
        }
    }

    #[test]
    fn delete_webhook_successfully() {
        let uuid = "696c5f93-1b7f-44bc-8d03-59440fcaa9a0";

        let webhook_get_interaction = |mut i: InteractionBuilder| {
            i.given(format!("a webhook with the uuid {} exists", uuid));
            i.request
                .get()
                .path(format!("/webhooks/{}", uuid))
                .header("Accept", "application/hal+json")
                .header("Accept", "application/json");
            i.response
                .status(200)
                .header("Content-Type", "application/hal+json;charset=utf-8")
                .json_body(json_pattern!({
                    "uuid": uuid,
                    "description": like!("an example webhook"),
                    "_links": {
                        "self": {
                            "href": term!("http:\\/\\/.*\\/webhooks\\/.*", "http://localhost/webhooks/{uuid}")
                        }
                    }
                }));
            i
        };

        let webhook_delete_interaction = |mut i: InteractionBuilder| {
            i.given(format!("a webhook with the uuid {} exists", uuid));
            i.request
                .delete()
                .path(format!("/webhooks/{}", uuid))
                .header("Accept", "application/hal+json");
            i.response.status(204);
            i
        };

        let mock_server = setup_mock_server(vec![
            index_interaction()(InteractionBuilder::new(
                "a request for the index resource",
                "",
            )),
            webhook_get_interaction(InteractionBuilder::new(
                "a request to get a webhook by UUID",
                "",
            )),
            webhook_delete_interaction(InteractionBuilder::new(
                "a request to delete a webhook",
                "",
            )),
        ]);
        let mock_server_url = mock_server.url();

        let matches = add_delete_webhook_subcommand()
            .get_matches_from(base_args(mock_server_url.as_str(), uuid));

        let result = delete_webhook(&matches);

        if result.is_err() {
            println!("Delete webhook error: {:?}", result.as_ref().err().unwrap());
        }
        assert!(result.is_ok());
        let message = result.unwrap();
        assert!(message.contains("successfully deleted"));
        assert!(message.contains(uuid));
    }

    #[test]
    fn delete_webhook_not_found() {
        let uuid = "non-existent-uuid";

        let webhook_get_interaction = |mut i: InteractionBuilder| {
            i.given(format!("a webhook with uuid {} does not exist", uuid));
            i.request
                .get()
                .path(format!("/webhooks/{}", uuid))
                .header("Accept", "application/hal+json")
                .header("Accept", "application/json");
            i.response.status(404);
            i
        };

        let mock_server = setup_mock_server(vec![
            index_interaction()(InteractionBuilder::new(
                "a request for the index resource",
                "",
            )),
            webhook_get_interaction(InteractionBuilder::new(
                "a request to get a non-existent webhook by UUID",
                "",
            )),
        ]);
        let mock_server_url = mock_server.url();

        let matches = add_delete_webhook_subcommand()
            .get_matches_from(base_args(mock_server_url.as_str(), uuid));

        let result = delete_webhook(&matches);

        assert!(result.is_ok());
        let message = result.unwrap();
        assert!(message.contains("was not found"));
        assert!(message.contains(uuid));
    }
}