stormchaser_cli/commands/
rules.rs1use crate::utils::{handle_response, parse_key_val_list, require_token};
2use anyhow::Result;
3use clap::Subcommand;
4use serde_json::json;
5
6#[derive(Subcommand)]
7pub enum RuleCommands {
8 List,
10 Create {
12 name: String,
13 #[arg(long)]
14 webhook_id: stormchaser_model::WebhookId,
15 #[arg(long)]
16 event_pattern: String,
17 #[arg(long)]
18 workflow: String,
19 #[arg(long)]
20 repo: String,
21 #[arg(long)]
22 path: String,
23 #[arg(long, default_value = "main")]
24 git_ref: String,
25 #[arg(long)]
26 description: Option<String>,
27 #[arg(short, long)]
29 mapping: Vec<String>,
30 },
31 Delete { id: stormchaser_model::RuleId },
33}
34
35pub async fn handle(
36 url: &str,
37 token: Option<&str>,
38 http_client: &reqwest_middleware::ClientWithMiddleware,
39 command: RuleCommands,
40) -> Result<()> {
41 match command {
42 RuleCommands::List => {
43 let token = require_token(token)?;
44 let res = http_client
45 .get(format!("{}/api/v1/rules", url))
46 .header(reqwest::header::AUTHORIZATION, format!("Bearer {}", token))
47 .send()
48 .await?;
49 handle_response(res).await?;
50 }
51 RuleCommands::Create {
52 name,
53 webhook_id,
54 event_pattern,
55 workflow,
56 repo,
57 path,
58 git_ref,
59 description,
60 mapping,
61 } => {
62 let mappings = parse_key_val_list(mapping);
63 let token = require_token(token)?;
64 let res = http_client
65 .post(format!("{}/api/v1/rules", url))
66 .header(reqwest::header::AUTHORIZATION, format!("Bearer {}", token))
67 .json(&json!({
68 "name": name,
69 "webhook_id": webhook_id,
70 "event_type_pattern": event_pattern,
71 "workflow_name": workflow,
72 "repo_url": repo,
73 "workflow_path": path,
74 "git_ref": git_ref,
75 "description": description,
76 "input_mappings": mappings,
77 }))
78 .send()
79 .await?;
80 handle_response(res).await?;
81 }
82 RuleCommands::Delete { id } => {
83 let token = require_token(token)?;
84 let res = http_client
85 .delete(format!("{}/api/v1/rules/{}", url, id))
86 .header(reqwest::header::AUTHORIZATION, format!("Bearer {}", token))
87 .send()
88 .await?;
89 handle_response(res).await?;
90 }
91 }
92 Ok(())
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98 use reqwest_middleware::ClientBuilder;
99 use wiremock::matchers::{header, method, path};
100 use wiremock::{Mock, MockServer, ResponseTemplate};
101
102 #[tokio::test]
103 async fn test_rules_list() {
104 let server = MockServer::start().await;
105 Mock::given(method("GET"))
106 .and(path("/api/v1/rules"))
107 .and(header(reqwest::header::AUTHORIZATION, "Bearer test-token"))
108 .respond_with(ResponseTemplate::new(200).set_body_json(json!([])))
109 .mount(&server)
110 .await;
111
112 let client = ClientBuilder::new(reqwest::Client::new()).build();
113 let cmd = RuleCommands::List;
114
115 let result = handle(&server.uri(), Some("test-token"), &client, cmd).await;
116 assert!(result.is_ok());
117 }
118
119 #[tokio::test]
120 async fn test_rules_create() {
121 let server = MockServer::start().await;
122 Mock::given(method("POST"))
123 .and(path("/api/v1/rules"))
124 .and(header(reqwest::header::AUTHORIZATION, "Bearer test-token"))
125 .respond_with(ResponseTemplate::new(200).set_body_json(json!({"status": "created"})))
126 .mount(&server)
127 .await;
128
129 let client = ClientBuilder::new(reqwest::Client::new()).build();
130 let cmd = RuleCommands::Create {
131 name: "test-rule".to_string(),
132 webhook_id: stormchaser_model::WebhookId::new_v4(),
133 event_pattern: "push".to_string(),
134 workflow: "my-wf".to_string(),
135 repo: "https://github.com/a/b".to_string(),
136 path: "wf.storm".to_string(),
137 git_ref: "main".to_string(),
138 description: Some("desc".to_string()),
139 mapping: vec!["key=value".to_string()],
140 };
141
142 let result = handle(&server.uri(), Some("test-token"), &client, cmd).await;
143 assert!(result.is_ok());
144 }
145
146 #[tokio::test]
147 async fn test_rules_delete() {
148 let server = MockServer::start().await;
149 let id = stormchaser_model::RuleId::new_v4();
150 Mock::given(method("DELETE"))
151 .and(path(format!("/api/v1/rules/{}", id)))
152 .and(header(reqwest::header::AUTHORIZATION, "Bearer test-token"))
153 .respond_with(ResponseTemplate::new(200).set_body_json(json!({"status": "deleted"})))
154 .mount(&server)
155 .await;
156
157 let client = ClientBuilder::new(reqwest::Client::new()).build();
158 let cmd = RuleCommands::Delete { id };
159
160 let result = handle(&server.uri(), Some("test-token"), &client, cmd).await;
161 assert!(result.is_ok());
162 }
163}