stormchaser_cli/commands/
webhooks.rs1use 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,
10 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 { id: stormchaser_model::WebhookId },
22 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 { 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(reqwest::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(reqwest::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(reqwest::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(reqwest::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(reqwest::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(reqwest::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(reqwest::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(reqwest::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(reqwest::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(reqwest::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}