Skip to main content

stormchaser_cli/commands/
webhooks.rs

1use crate::utils::{handle_response, require_token};
2use anyhow::Result;
3use clap::Subcommand;
4use serde_json::json;
5
6#[derive(Subcommand)]
7pub enum WebhookCommands {
8    /// List webhooks
9    List,
10    /// Create a new webhook
11    Create {
12        name: String,
13        #[arg(long)]
14        source_type: String,
15        #[arg(long)]
16        secret: Option<String>,
17        #[arg(long)]
18        description: Option<String>,
19    },
20    /// Get webhook details
21    Get { id: stormchaser_model::WebhookId },
22    /// Update a webhook
23    Update {
24        id: stormchaser_model::WebhookId,
25        #[arg(long)]
26        name: Option<String>,
27        #[arg(long)]
28        source_type: Option<String>,
29        #[arg(long)]
30        secret: Option<String>,
31        #[arg(long)]
32        description: Option<String>,
33        #[arg(long)]
34        is_active: Option<bool>,
35    },
36    /// Delete a webhook
37    Delete { id: stormchaser_model::WebhookId },
38}
39
40pub async fn handle(
41    url: &str,
42    token: Option<&str>,
43    http_client: &reqwest_middleware::ClientWithMiddleware,
44    command: WebhookCommands,
45) -> Result<()> {
46    match command {
47        WebhookCommands::List => {
48            let token = require_token(token)?;
49            let res = http_client
50                .get(format!("{}/api/v1/webhooks", url))
51                .header("Authorization", format!("Bearer {}", token))
52                .send()
53                .await?;
54            handle_response(res).await?;
55        }
56        WebhookCommands::Create {
57            name,
58            source_type,
59            secret,
60            description,
61        } => {
62            let token = require_token(token)?;
63            let res = http_client
64                .post(format!("{}/api/v1/webhooks", url))
65                .header("Authorization", format!("Bearer {}", token))
66                .json(&json!({
67                    "name": name,
68                    "source_type": source_type,
69                    "secret_token": secret,
70                    "description": description,
71                }))
72                .send()
73                .await?;
74            handle_response(res).await?;
75        }
76        WebhookCommands::Get { id } => {
77            let token = require_token(token)?;
78            let res = http_client
79                .get(format!("{}/api/v1/webhooks/{}", url, id))
80                .header("Authorization", format!("Bearer {}", token))
81                .send()
82                .await?;
83            handle_response(res).await?;
84        }
85        WebhookCommands::Delete { id } => {
86            let token = require_token(token)?;
87            let res = http_client
88                .delete(format!("{}/api/v1/webhooks/{}", url, id))
89                .header("Authorization", format!("Bearer {}", token))
90                .send()
91                .await?;
92            handle_response(res).await?;
93        }
94        WebhookCommands::Update {
95            id,
96            name,
97            source_type,
98            secret,
99            description,
100            is_active,
101        } => {
102            let token = require_token(token)?;
103            let mut body = serde_json::Map::new();
104            if let Some(n) = name {
105                body.insert("name".to_string(), json!(n));
106            }
107            if let Some(st) = source_type {
108                body.insert("source_type".to_string(), json!(st));
109            }
110            if let Some(sec) = secret {
111                body.insert("secret_token".to_string(), json!(sec));
112            }
113            if let Some(desc) = description {
114                body.insert("description".to_string(), json!(desc));
115            }
116            if let Some(ia) = is_active {
117                body.insert("is_active".to_string(), json!(ia));
118            }
119
120            let res = http_client
121                .patch(format!("{}/api/v1/webhooks/{}", url, id))
122                .header("Authorization", format!("Bearer {}", token))
123                .json(&body)
124                .send()
125                .await?;
126            handle_response(res).await?;
127        }
128    }
129    Ok(())
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135    use reqwest_middleware::ClientBuilder;
136    use wiremock::matchers::{header, method, path};
137    use wiremock::{Mock, MockServer, ResponseTemplate};
138
139    #[tokio::test]
140    async fn test_webhook_list() {
141        let server = MockServer::start().await;
142        Mock::given(method("GET"))
143            .and(path("/api/v1/webhooks"))
144            .and(header("Authorization", "Bearer test-token"))
145            .respond_with(ResponseTemplate::new(200).set_body_json(json!([])))
146            .mount(&server)
147            .await;
148
149        let client = ClientBuilder::new(reqwest::Client::new()).build();
150        let cmd = WebhookCommands::List;
151
152        let result = handle(&server.uri(), Some("test-token"), &client, cmd).await;
153        assert!(result.is_ok());
154    }
155
156    #[tokio::test]
157    async fn test_webhook_create() {
158        let server = MockServer::start().await;
159        Mock::given(method("POST"))
160            .and(path("/api/v1/webhooks"))
161            .and(header("Authorization", "Bearer test-token"))
162            .respond_with(ResponseTemplate::new(200).set_body_json(json!({"status": "created"})))
163            .mount(&server)
164            .await;
165
166        let client = ClientBuilder::new(reqwest::Client::new()).build();
167        let cmd = WebhookCommands::Create {
168            name: "test-webhook".to_string(),
169            source_type: "github".to_string(),
170            secret: Some("secret".to_string()),
171            description: None,
172        };
173
174        let result = handle(&server.uri(), Some("test-token"), &client, cmd).await;
175        assert!(result.is_ok());
176    }
177
178    #[tokio::test]
179    async fn test_webhook_get() {
180        let server = MockServer::start().await;
181        let id = stormchaser_model::WebhookId::new_v4();
182        Mock::given(method("GET"))
183            .and(path(format!("/api/v1/webhooks/{}", id)))
184            .and(header("Authorization", "Bearer test-token"))
185            .respond_with(ResponseTemplate::new(200).set_body_json(json!({"id": id})))
186            .mount(&server)
187            .await;
188
189        let client = ClientBuilder::new(reqwest::Client::new()).build();
190        let cmd = WebhookCommands::Get { id };
191
192        let result = handle(&server.uri(), Some("test-token"), &client, cmd).await;
193        assert!(result.is_ok());
194    }
195
196    #[tokio::test]
197    async fn test_webhook_update() {
198        let server = MockServer::start().await;
199        let id = stormchaser_model::WebhookId::new_v4();
200        Mock::given(method("PATCH"))
201            .and(path(format!("/api/v1/webhooks/{}", id)))
202            .and(header("Authorization", "Bearer test-token"))
203            .respond_with(ResponseTemplate::new(200).set_body_json(json!({"status": "updated"})))
204            .mount(&server)
205            .await;
206
207        let client = ClientBuilder::new(reqwest::Client::new()).build();
208        let cmd = WebhookCommands::Update {
209            id,
210            name: Some("new-name".to_string()),
211            source_type: None,
212            secret: None,
213            description: None,
214            is_active: Some(false),
215        };
216
217        let result = handle(&server.uri(), Some("test-token"), &client, cmd).await;
218        assert!(result.is_ok());
219    }
220
221    #[tokio::test]
222    async fn test_webhook_delete() {
223        let server = MockServer::start().await;
224        let id = stormchaser_model::WebhookId::new_v4();
225        Mock::given(method("DELETE"))
226            .and(path(format!("/api/v1/webhooks/{}", id)))
227            .and(header("Authorization", "Bearer test-token"))
228            .respond_with(ResponseTemplate::new(200).set_body_json(json!({"status": "deleted"})))
229            .mount(&server)
230            .await;
231
232        let client = ClientBuilder::new(reqwest::Client::new()).build();
233        let cmd = WebhookCommands::Delete { id };
234
235        let result = handle(&server.uri(), Some("test-token"), &client, cmd).await;
236        assert!(result.is_ok());
237    }
238}