diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5d7bd52..5782c82 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,4 +14,4 @@ jobs: - uses: Swatinem/rust-cache@v2 - run: cargo build --release - run: cargo test - - run: cargo clippy -- -D warnings + - run: cargo clippy --all-targets -- -D warnings diff --git a/src/discovery.rs b/src/discovery.rs index 149f0f5..0793451 100644 --- a/src/discovery.rs +++ b/src/discovery.rs @@ -97,8 +97,8 @@ mod tests { #[test] fn test_discover_tools_returns_vec() { let tools = discover_tools(); - // We can't guarantee which tools are installed, but it should not panic - assert!(tools.len() >= 0); + // We can't guarantee which tools are installed, but it should return a valid vec + let _ = tools.len(); } #[test] diff --git a/src/executor.rs b/src/executor.rs index c76fd76..dfd13b0 100644 --- a/src/executor.rs +++ b/src/executor.rs @@ -1,3 +1,5 @@ +#![allow(dead_code)] + use serde::{Deserialize, Serialize}; use std::process::Command as SysCommand; use std::time::Instant; @@ -37,6 +39,7 @@ pub fn execute_task(cmd: &TaskCommand) -> TaskResult { let result = match cmd.tool_name.as_str() { "sigops.restart" => execute_restart(cmd), "sigops.http" => execute_http_sync(cmd), + "sigops.notify_slack" => execute_notify_slack(cmd), "sigops.condition" => execute_condition(cmd), "sigops.wait" => execute_wait(cmd), _ => Err(format!("unknown tool: {}", cmd.tool_name)), @@ -120,6 +123,56 @@ fn execute_http_sync(cmd: &TaskCommand) -> Result { })) } +fn execute_notify_slack(cmd: &TaskCommand) -> Result { + let channel = cmd.input["channel"] + .as_str() + .ok_or("missing 'channel' field")?; + let message = cmd.input["message"] + .as_str() + .ok_or("missing 'message' field")?; + + let webhook_url = cmd.input["webhookUrl"] + .as_str() + .unwrap_or("https://hooks.slack.com/services/placeholder"); + + let payload = serde_json::json!({ + "channel": channel, + "text": message, + }); + + let payload_str = + serde_json::to_string(&payload).map_err(|e| format!("json encode failed: {}", e))?; + + let output = SysCommand::new("curl") + .args([ + "-s", + "-o", + "/dev/null", + "-w", + "%{http_code}", + "-X", + "POST", + "-H", + "Content-Type: application/json", + "-d", + &payload_str, + webhook_url, + ]) + .output() + .map_err(|e| format!("curl failed: {}", e))?; + + let status_code = String::from_utf8_lossy(&output.stdout) + .trim() + .parse::() + .unwrap_or(0); + + Ok(serde_json::json!({ + "ok": (200..300).contains(&status_code), + "channel": channel, + "status": status_code, + })) +} + fn execute_condition(cmd: &TaskCommand) -> Result { let expr = cmd.input["expression"] .as_str() @@ -276,4 +329,28 @@ mod tests { assert_eq!(result.status, TaskStatus::Failed); assert!(result.error.unwrap().contains("invalid service name")); } + + #[test] + fn test_notify_slack_missing_channel() { + let cmd = TaskCommand { + task_id: "t1".to_string(), + tool_name: "sigops.notify_slack".to_string(), + input: serde_json::json!({"message": "hello"}), + }; + let result = execute_task(&cmd); + assert_eq!(result.status, TaskStatus::Failed); + assert!(result.error.unwrap().contains("channel")); + } + + #[test] + fn test_notify_slack_missing_message() { + let cmd = TaskCommand { + task_id: "t1".to_string(), + tool_name: "sigops.notify_slack".to_string(), + input: serde_json::json!({"channel": "#alerts"}), + }; + let result = execute_task(&cmd); + assert_eq!(result.status, TaskStatus::Failed); + assert!(result.error.unwrap().contains("message")); + } }