use anyhow::Result;
use clap::{Parser, Subcommand};
use crate::cli::atlassian::helpers::create_client;
#[derive(Parser)]
pub struct LabelCommand {
#[command(subcommand)]
pub command: LabelSubcommands,
}
#[derive(Subcommand)]
pub enum LabelSubcommands {
Add(AddCommand),
Remove(RemoveCommand),
}
impl LabelCommand {
pub async fn execute(self) -> Result<()> {
match self.command {
LabelSubcommands::Add(cmd) => cmd.execute().await,
LabelSubcommands::Remove(cmd) => cmd.execute().await,
}
}
}
#[derive(Parser)]
pub struct AddCommand {
pub key: String,
#[arg(long, value_delimiter = ',')]
pub labels: Vec<String>,
}
impl AddCommand {
pub async fn execute(self) -> Result<()> {
if self.labels.is_empty() {
anyhow::bail!("No labels supplied: pass --labels a,b,c");
}
let (client, _instance_url) = create_client()?;
client
.modify_issue_labels(&self.key, &self.labels, &[])
.await?;
println!("Added label(s) {} to {}.", self.labels.join(", "), self.key);
Ok(())
}
}
#[derive(Parser)]
pub struct RemoveCommand {
pub key: String,
#[arg(long, value_delimiter = ',')]
pub labels: Vec<String>,
}
impl RemoveCommand {
pub async fn execute(self) -> Result<()> {
if self.labels.is_empty() {
anyhow::bail!("No labels supplied: pass --labels a,b,c");
}
let (client, _instance_url) = create_client()?;
client
.modify_issue_labels(&self.key, &[], &self.labels)
.await?;
println!(
"Removed label(s) {} from {}.",
self.labels.join(", "),
self.key
);
Ok(())
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn label_command_add_variant() {
let cmd = LabelCommand {
command: LabelSubcommands::Add(AddCommand {
key: "PROJ-1".to_string(),
labels: vec!["backend".to_string()],
}),
};
assert!(matches!(cmd.command, LabelSubcommands::Add(_)));
}
#[test]
fn label_command_remove_variant() {
let cmd = LabelCommand {
command: LabelSubcommands::Remove(RemoveCommand {
key: "PROJ-1".to_string(),
labels: vec!["stale".to_string()],
}),
};
assert!(matches!(cmd.command, LabelSubcommands::Remove(_)));
}
#[tokio::test]
async fn add_with_no_labels_errors_before_client() {
let cmd = AddCommand {
key: "PROJ-1".to_string(),
labels: vec![],
};
let err = cmd.execute().await.unwrap_err();
assert!(err.to_string().contains("No labels supplied"));
}
#[tokio::test]
async fn remove_with_no_labels_errors_before_client() {
let cmd = RemoveCommand {
key: "PROJ-1".to_string(),
labels: vec![],
};
let err = cmd.execute().await.unwrap_err();
assert!(err.to_string().contains("No labels supplied"));
}
fn issue_put_mock() -> wiremock::Mock {
wiremock::Mock::given(wiremock::matchers::method("PUT"))
.and(wiremock::matchers::path("/rest/api/3/issue/PROJ-1"))
.respond_with(wiremock::ResponseTemplate::new(204))
.expect(1)
}
#[tokio::test]
async fn add_execute_drives_create_client_and_puts() {
use crate::test_support::atlassian_env::AtlassianEnvGuard;
let server = wiremock::MockServer::start().await;
issue_put_mock().mount(&server).await;
let _env = AtlassianEnvGuard::new(&server.uri(), "u@t.com", "tok");
AddCommand {
key: "PROJ-1".to_string(),
labels: vec!["backend".to_string(), "reviewed".to_string()],
}
.execute()
.await
.unwrap();
}
#[tokio::test]
async fn remove_execute_drives_create_client_and_puts() {
use crate::test_support::atlassian_env::AtlassianEnvGuard;
let server = wiremock::MockServer::start().await;
issue_put_mock().mount(&server).await;
let _env = AtlassianEnvGuard::new(&server.uri(), "u@t.com", "tok");
LabelCommand {
command: LabelSubcommands::Remove(RemoveCommand {
key: "PROJ-1".to_string(),
labels: vec!["stale".to_string()],
}),
}
.execute()
.await
.unwrap();
}
}