1pub(crate) mod attachment;
4pub(crate) mod children;
5pub(crate) mod comment;
6pub(crate) mod compare;
7pub(crate) mod create;
8pub(crate) mod delete;
9pub(crate) mod download;
10pub(crate) mod edit;
11pub(crate) mod history;
12pub(crate) mod label;
13pub(crate) mod move_page;
14pub(crate) mod read;
15pub(crate) mod search;
16pub(crate) mod user;
17pub(crate) mod write;
18
19use anyhow::Result;
20use clap::{Parser, Subcommand};
21
22#[derive(Parser)]
24pub struct ConfluenceCommand {
25 #[command(subcommand)]
27 pub command: ConfluenceSubcommands,
28}
29
30#[derive(Subcommand)]
32pub enum ConfluenceSubcommands {
33 Comment(comment::CommentCommand),
35 Read(read::ReadCommand),
37 Write(write::WriteCommand),
39 Edit(edit::EditCommand),
41 Search(search::SearchCommand),
43 Create(create::CreateCommand),
45 Delete(delete::DeleteCommand),
47 Move(move_page::MoveCommand),
49 Label(label::LabelCommand),
51 Attachment(attachment::AttachmentCommand),
53 Download(download::DownloadCommand),
55 Children(children::ChildrenCommand),
57 History(history::HistoryCommand),
59 Compare(compare::CompareCommandGroup),
61 User(user::UserCommand),
63}
64
65impl ConfluenceCommand {
66 pub async fn execute(self) -> Result<()> {
68 match self.command {
69 ConfluenceSubcommands::Comment(cmd) => cmd.execute().await,
70 ConfluenceSubcommands::Read(cmd) => cmd.execute().await,
71 ConfluenceSubcommands::Write(cmd) => cmd.execute().await,
72 ConfluenceSubcommands::Edit(cmd) => cmd.execute().await,
73 ConfluenceSubcommands::Search(cmd) => cmd.execute().await,
74 ConfluenceSubcommands::Create(cmd) => cmd.execute().await,
75 ConfluenceSubcommands::Label(cmd) => cmd.execute().await,
76 ConfluenceSubcommands::Attachment(cmd) => cmd.execute().await,
77 ConfluenceSubcommands::Delete(cmd) => cmd.execute().await,
78 ConfluenceSubcommands::Move(cmd) => cmd.execute().await,
79 ConfluenceSubcommands::Download(cmd) => cmd.execute().await,
80 ConfluenceSubcommands::Children(cmd) => cmd.execute().await,
81 ConfluenceSubcommands::History(cmd) => cmd.execute().await,
82 ConfluenceSubcommands::Compare(cmd) => cmd.execute().await,
83 ConfluenceSubcommands::User(cmd) => cmd.execute().await,
84 }
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91 use crate::cli::atlassian::format::{ContentFormat, OutputFormat};
92
93 #[test]
94 fn confluence_subcommands_comment_variant() {
95 let cmd = ConfluenceCommand {
96 command: ConfluenceSubcommands::Comment(comment::CommentCommand {
97 command: comment::CommentSubcommands::List(comment::ListCommand {
98 id: "12345".to_string(),
99 limit: 25,
100 output: OutputFormat::Table,
101 }),
102 }),
103 };
104 assert!(matches!(cmd.command, ConfluenceSubcommands::Comment(_)));
105 }
106
107 #[test]
108 fn confluence_subcommands_read_variant() {
109 let cmd = ConfluenceCommand {
110 command: ConfluenceSubcommands::Read(read::ReadCommand {
111 id: "12345".to_string(),
112 output: None,
113 format: ContentFormat::Jfm,
114 }),
115 };
116 assert!(matches!(cmd.command, ConfluenceSubcommands::Read(_)));
117 }
118
119 #[test]
120 fn confluence_subcommands_write_variant() {
121 let cmd = ConfluenceCommand {
122 command: ConfluenceSubcommands::Write(write::WriteCommand {
123 id: "12345".to_string(),
124 file: None,
125 format: ContentFormat::Adf,
126 force: false,
127 dry_run: false,
128 }),
129 };
130 assert!(matches!(cmd.command, ConfluenceSubcommands::Write(_)));
131 }
132
133 #[test]
134 fn confluence_subcommands_edit_variant() {
135 let cmd = ConfluenceCommand {
136 command: ConfluenceSubcommands::Edit(edit::EditCommand {
137 id: "12345".to_string(),
138 }),
139 };
140 assert!(matches!(cmd.command, ConfluenceSubcommands::Edit(_)));
141 }
142
143 #[test]
144 fn confluence_subcommands_search_variant() {
145 let cmd = ConfluenceCommand {
146 command: ConfluenceSubcommands::Search(search::SearchCommand {
147 cql: Some("space = ENG".to_string()),
148 space: None,
149 title: None,
150 limit: 25,
151 output: OutputFormat::Table,
152 }),
153 };
154 assert!(matches!(cmd.command, ConfluenceSubcommands::Search(_)));
155 }
156
157 #[test]
158 fn confluence_subcommands_create_variant() {
159 let cmd = ConfluenceCommand {
160 command: ConfluenceSubcommands::Create(create::CreateCommand {
161 file: None,
162 format: ContentFormat::Jfm,
163 space: Some("ENG".to_string()),
164 title: Some("Test".to_string()),
165 parent: None,
166 dry_run: false,
167 }),
168 };
169 assert!(matches!(cmd.command, ConfluenceSubcommands::Create(_)));
170 }
171
172 #[test]
173 fn confluence_subcommands_label_variant() {
174 let cmd = ConfluenceCommand {
175 command: ConfluenceSubcommands::Label(label::LabelCommand {
176 command: label::LabelSubcommands::List(label::ListCommand {
177 id: "12345".to_string(),
178 output: OutputFormat::Table,
179 }),
180 }),
181 };
182 assert!(matches!(cmd.command, ConfluenceSubcommands::Label(_)));
183 }
184
185 #[test]
186 fn confluence_subcommands_attachment_variant() {
187 let cmd = ConfluenceCommand {
188 command: ConfluenceSubcommands::Attachment(attachment::AttachmentCommand {
189 command: attachment::AttachmentSubcommands::List(attachment::ListCommand {
190 page_id: "12345".to_string(),
191 cursor: None,
192 limit: 25,
193 output: OutputFormat::Table,
194 }),
195 }),
196 };
197 assert!(matches!(cmd.command, ConfluenceSubcommands::Attachment(_)));
198 }
199
200 #[test]
201 fn confluence_subcommands_delete_variant() {
202 let cmd = ConfluenceCommand {
203 command: ConfluenceSubcommands::Delete(delete::DeleteCommand {
204 id: "12345".to_string(),
205 force: true,
206 dry_run: false,
207 purge: false,
208 }),
209 };
210 assert!(matches!(cmd.command, ConfluenceSubcommands::Delete(_)));
211 }
212
213 #[test]
214 fn confluence_subcommands_move_variant() {
215 let cmd = ConfluenceCommand {
216 command: ConfluenceSubcommands::Move(move_page::MoveCommand {
217 id: "12345".to_string(),
218 target: "456".to_string(),
219 position: move_page::MovePosition::Append,
220 }),
221 };
222 assert!(matches!(cmd.command, ConfluenceSubcommands::Move(_)));
223 }
224
225 #[tokio::test]
229 async fn confluence_command_execute_move_dispatch() {
230 std::env::set_var("ATLASSIAN_INSTANCE_URL", "http://127.0.0.1:1");
231 std::env::set_var("ATLASSIAN_EMAIL", "test@example.com");
232 std::env::set_var("ATLASSIAN_API_TOKEN", "fake-token");
233
234 let cmd = ConfluenceCommand {
235 command: ConfluenceSubcommands::Move(move_page::MoveCommand {
236 id: "12345".to_string(),
237 target: "456".to_string(),
238 position: move_page::MovePosition::Append,
239 }),
240 };
241 let _ = cmd.execute().await;
242
243 std::env::remove_var("ATLASSIAN_INSTANCE_URL");
244 std::env::remove_var("ATLASSIAN_EMAIL");
245 std::env::remove_var("ATLASSIAN_API_TOKEN");
246 }
247
248 #[test]
249 fn confluence_subcommands_user_variant() {
250 let cmd = ConfluenceCommand {
251 command: ConfluenceSubcommands::User(user::UserCommand {
252 command: user::UserSubcommands::Search(user::UserSearchCommand {
253 query: "alice".to_string(),
254 limit: 25,
255 output: OutputFormat::Table,
256 }),
257 }),
258 };
259 assert!(matches!(cmd.command, ConfluenceSubcommands::User(_)));
260 }
261
262 #[test]
263 fn confluence_subcommands_children_variant() {
264 let cmd = ConfluenceCommand {
265 command: ConfluenceSubcommands::Children(children::ChildrenCommand {
266 id: Some("12345".to_string()),
267 space: None,
268 recursive: false,
269 max_depth: 0,
270 output: OutputFormat::Table,
271 }),
272 };
273 assert!(matches!(cmd.command, ConfluenceSubcommands::Children(_)));
274 }
275
276 #[test]
277 fn confluence_subcommands_history_variant() {
278 let cmd = ConfluenceCommand {
279 command: ConfluenceSubcommands::History(history::HistoryCommand {
280 id: "12345".to_string(),
281 since: None,
282 limit: 20,
283 output: OutputFormat::Table,
284 }),
285 };
286 assert!(matches!(cmd.command, ConfluenceSubcommands::History(_)));
287 }
288
289 #[tokio::test]
294 async fn confluence_command_execute_history_dispatch() {
295 std::env::set_var("ATLASSIAN_INSTANCE_URL", "http://127.0.0.1:1");
296 std::env::set_var("ATLASSIAN_EMAIL", "test@example.com");
297 std::env::set_var("ATLASSIAN_API_TOKEN", "fake-token");
298
299 let cmd = ConfluenceCommand {
300 command: ConfluenceSubcommands::History(history::HistoryCommand {
301 id: "12345".to_string(),
302 since: None,
303 limit: 20,
304 output: OutputFormat::Table,
305 }),
306 };
307 let _ = cmd.execute().await;
308
309 std::env::remove_var("ATLASSIAN_INSTANCE_URL");
310 std::env::remove_var("ATLASSIAN_EMAIL");
311 std::env::remove_var("ATLASSIAN_API_TOKEN");
312 }
313
314 #[tokio::test]
319 #[allow(clippy::await_holding_lock)]
320 async fn confluence_command_execute_children_dispatch() {
321 let _lock = crate::atlassian::auth::test_util::AUTH_ENV_MUTEX
324 .lock()
325 .unwrap_or_else(std::sync::PoisonError::into_inner);
326
327 std::env::set_var("ATLASSIAN_INSTANCE_URL", "http://127.0.0.1:1");
328 std::env::set_var("ATLASSIAN_EMAIL", "test@example.com");
329 std::env::set_var("ATLASSIAN_API_TOKEN", "fake-token");
330
331 let cmd = ConfluenceCommand {
332 command: ConfluenceSubcommands::Children(children::ChildrenCommand {
333 id: Some("12345".to_string()),
334 space: None,
335 recursive: false,
336 max_depth: 0,
337 output: OutputFormat::Table,
338 }),
339 };
340 let _ = cmd.execute().await;
341
342 std::env::remove_var("ATLASSIAN_INSTANCE_URL");
343 std::env::remove_var("ATLASSIAN_EMAIL");
344 std::env::remove_var("ATLASSIAN_API_TOKEN");
345 }
346
347 #[tokio::test]
349 async fn confluence_command_execute_attachment_dispatch() {
350 std::env::set_var("ATLASSIAN_INSTANCE_URL", "http://127.0.0.1:1");
351 std::env::set_var("ATLASSIAN_EMAIL", "test@example.com");
352 std::env::set_var("ATLASSIAN_API_TOKEN", "fake-token");
353
354 let cmd = ConfluenceCommand {
355 command: ConfluenceSubcommands::Attachment(attachment::AttachmentCommand {
356 command: attachment::AttachmentSubcommands::List(attachment::ListCommand {
357 page_id: "12345".to_string(),
358 cursor: None,
359 limit: 25,
360 output: OutputFormat::Table,
361 }),
362 }),
363 };
364 let _ = cmd.execute().await;
365
366 std::env::remove_var("ATLASSIAN_INSTANCE_URL");
367 std::env::remove_var("ATLASSIAN_EMAIL");
368 std::env::remove_var("ATLASSIAN_API_TOKEN");
369 }
370
371 #[test]
372 fn confluence_subcommands_download_variant() {
373 let cmd = ConfluenceCommand {
374 command: ConfluenceSubcommands::Download(download::DownloadCommand {
375 id: Some("12345".to_string()),
376 space: None,
377 output_dir: std::path::PathBuf::from("."),
378 format: ContentFormat::Jfm,
379 concurrency: 8,
380 max_depth: 0,
381 title_filter: None,
382 resume: false,
383 on_conflict: download::OnConflict::Backup,
384 }),
385 };
386 assert!(matches!(cmd.command, ConfluenceSubcommands::Download(_)));
387 }
388
389 #[test]
390 fn confluence_subcommands_download_space_variant() {
391 let cmd = ConfluenceCommand {
392 command: ConfluenceSubcommands::Download(download::DownloadCommand {
393 id: None,
394 space: Some("AD".to_string()),
395 output_dir: std::path::PathBuf::from("./AD"),
396 format: ContentFormat::Jfm,
397 concurrency: 8,
398 max_depth: 0,
399 title_filter: Some("architecture".to_string()),
400 resume: false,
401 on_conflict: download::OnConflict::Backup,
402 }),
403 };
404 assert!(matches!(cmd.command, ConfluenceSubcommands::Download(_)));
405 }
406
407 #[tokio::test]
412 async fn confluence_command_execute_compare_dispatch() {
413 std::env::set_var("ATLASSIAN_INSTANCE_URL", "http://127.0.0.1:1");
414 std::env::set_var("ATLASSIAN_EMAIL", "test@example.com");
415 std::env::set_var("ATLASSIAN_API_TOKEN", "fake-token");
416
417 let cmd = ConfluenceCommand {
418 command: ConfluenceSubcommands::Compare(compare::CompareCommandGroup {
419 command: compare::CompareSubcommands::Run(compare::CompareCommand {
420 id: "12345".to_string(),
421 from: "previous".to_string(),
422 to: "latest".to_string(),
423 detail: compare::DetailArg::Outline,
424 include: "body".to_string(),
425 ignore_whitespace: true,
426 min_change_chars: 0,
427 filter_sections: Vec::new(),
428 budget: 16384,
429 output: OutputFormat::Yaml,
430 }),
431 }),
432 };
433 let _ = cmd.execute().await;
434
435 std::env::remove_var("ATLASSIAN_INSTANCE_URL");
436 std::env::remove_var("ATLASSIAN_EMAIL");
437 std::env::remove_var("ATLASSIAN_API_TOKEN");
438 }
439}