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 space;
17pub(crate) mod user;
18pub(crate) mod write;
19
20use anyhow::Result;
21use clap::{Parser, Subcommand};
22
23#[derive(Parser)]
25pub struct ConfluenceCommand {
26 #[command(subcommand)]
28 pub command: ConfluenceSubcommands,
29}
30
31#[derive(Subcommand)]
33pub enum ConfluenceSubcommands {
34 Comment(comment::CommentCommand),
36 Read(read::ReadCommand),
43 Write(write::WriteCommand),
53 Edit(edit::EditCommand),
55 Search(search::SearchCommand),
57 Create(create::CreateCommand),
59 Delete(delete::DeleteCommand),
61 Move(move_page::MoveCommand),
63 Label(label::LabelCommand),
65 Attachment(attachment::AttachmentCommand),
67 Download(download::DownloadCommand),
69 Children(children::ChildrenCommand),
71 History(history::HistoryCommand),
73 Compare(compare::CompareCommandGroup),
75 User(user::UserCommand),
77 Space(space::SpaceCommand),
79}
80
81impl ConfluenceCommand {
82 pub async fn execute(self) -> Result<()> {
84 match self.command {
85 ConfluenceSubcommands::Comment(cmd) => cmd.execute().await,
86 ConfluenceSubcommands::Read(cmd) => cmd.execute().await,
87 ConfluenceSubcommands::Write(cmd) => cmd.execute().await,
88 ConfluenceSubcommands::Edit(cmd) => cmd.execute().await,
89 ConfluenceSubcommands::Search(cmd) => cmd.execute().await,
90 ConfluenceSubcommands::Create(cmd) => cmd.execute().await,
91 ConfluenceSubcommands::Label(cmd) => cmd.execute().await,
92 ConfluenceSubcommands::Attachment(cmd) => cmd.execute().await,
93 ConfluenceSubcommands::Delete(cmd) => cmd.execute().await,
94 ConfluenceSubcommands::Move(cmd) => cmd.execute().await,
95 ConfluenceSubcommands::Download(cmd) => cmd.execute().await,
96 ConfluenceSubcommands::Children(cmd) => cmd.execute().await,
97 ConfluenceSubcommands::History(cmd) => cmd.execute().await,
98 ConfluenceSubcommands::Compare(cmd) => cmd.execute().await,
99 ConfluenceSubcommands::User(cmd) => cmd.execute().await,
100 ConfluenceSubcommands::Space(cmd) => cmd.execute().await,
101 }
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use crate::cli::atlassian::format::{ContentFormat, OutputFormat};
109
110 #[test]
111 fn confluence_subcommands_comment_variant() {
112 let cmd = ConfluenceCommand {
113 command: ConfluenceSubcommands::Comment(comment::CommentCommand {
114 command: comment::CommentSubcommands::List(comment::ListCommand {
115 id: "12345".to_string(),
116 kind: comment::CommentKindFilter::All,
117 limit: 25,
118 output: OutputFormat::Table,
119 }),
120 }),
121 };
122 assert!(matches!(cmd.command, ConfluenceSubcommands::Comment(_)));
123 }
124
125 #[test]
126 fn confluence_subcommands_read_variant() {
127 let cmd = ConfluenceCommand {
128 command: ConfluenceSubcommands::Read(read::ReadCommand {
129 id: "12345".to_string(),
130 out_file: None,
131 output: None,
132 format: ContentFormat::Jfm,
133 version: None,
134 }),
135 };
136 assert!(matches!(cmd.command, ConfluenceSubcommands::Read(_)));
137 }
138
139 #[test]
140 fn confluence_subcommands_write_variant() {
141 let cmd = ConfluenceCommand {
142 command: ConfluenceSubcommands::Write(write::WriteCommand {
143 id: "12345".to_string(),
144 file: None,
145 format: ContentFormat::Adf,
146 force: false,
147 dry_run: false,
148 }),
149 };
150 assert!(matches!(cmd.command, ConfluenceSubcommands::Write(_)));
151 }
152
153 #[test]
154 fn confluence_subcommands_edit_variant() {
155 let cmd = ConfluenceCommand {
156 command: ConfluenceSubcommands::Edit(edit::EditCommand {
157 id: "12345".to_string(),
158 }),
159 };
160 assert!(matches!(cmd.command, ConfluenceSubcommands::Edit(_)));
161 }
162
163 #[test]
164 fn confluence_subcommands_search_variant() {
165 let cmd = ConfluenceCommand {
166 command: ConfluenceSubcommands::Search(search::SearchCommand {
167 cql: Some("space = ENG".to_string()),
168 space: None,
169 title: None,
170 limit: 25,
171 output: OutputFormat::Table,
172 }),
173 };
174 assert!(matches!(cmd.command, ConfluenceSubcommands::Search(_)));
175 }
176
177 #[test]
178 fn confluence_subcommands_create_variant() {
179 let cmd = ConfluenceCommand {
180 command: ConfluenceSubcommands::Create(create::CreateCommand {
181 file: None,
182 format: ContentFormat::Jfm,
183 space: Some("ENG".to_string()),
184 title: Some("Test".to_string()),
185 parent: None,
186 dry_run: false,
187 }),
188 };
189 assert!(matches!(cmd.command, ConfluenceSubcommands::Create(_)));
190 }
191
192 #[test]
193 fn confluence_subcommands_label_variant() {
194 let cmd = ConfluenceCommand {
195 command: ConfluenceSubcommands::Label(label::LabelCommand {
196 command: label::LabelSubcommands::List(label::ListCommand {
197 id: "12345".to_string(),
198 output: OutputFormat::Table,
199 }),
200 }),
201 };
202 assert!(matches!(cmd.command, ConfluenceSubcommands::Label(_)));
203 }
204
205 #[test]
206 fn confluence_subcommands_attachment_variant() {
207 let cmd = ConfluenceCommand {
208 command: ConfluenceSubcommands::Attachment(attachment::AttachmentCommand {
209 command: attachment::AttachmentSubcommands::List(attachment::ListCommand {
210 page_id: "12345".to_string(),
211 cursor: None,
212 limit: 25,
213 output: OutputFormat::Table,
214 }),
215 }),
216 };
217 assert!(matches!(cmd.command, ConfluenceSubcommands::Attachment(_)));
218 }
219
220 #[test]
221 fn confluence_subcommands_delete_variant() {
222 let cmd = ConfluenceCommand {
223 command: ConfluenceSubcommands::Delete(delete::DeleteCommand {
224 id: "12345".to_string(),
225 force: true,
226 dry_run: false,
227 purge: false,
228 }),
229 };
230 assert!(matches!(cmd.command, ConfluenceSubcommands::Delete(_)));
231 }
232
233 #[test]
234 fn confluence_subcommands_move_variant() {
235 let cmd = ConfluenceCommand {
236 command: ConfluenceSubcommands::Move(move_page::MoveCommand {
237 id: "12345".to_string(),
238 target: "456".to_string(),
239 position: move_page::MovePosition::Append,
240 }),
241 };
242 assert!(matches!(cmd.command, ConfluenceSubcommands::Move(_)));
243 }
244
245 #[tokio::test]
249 #[allow(clippy::await_holding_lock)]
250 async fn confluence_command_execute_move_dispatch() {
251 let _lock = crate::atlassian::auth::test_util::AUTH_ENV_MUTEX
253 .lock()
254 .unwrap_or_else(std::sync::PoisonError::into_inner);
255
256 std::env::set_var("ATLASSIAN_INSTANCE_URL", "http://127.0.0.1:1");
257 std::env::set_var("ATLASSIAN_EMAIL", "test@example.com");
258 std::env::set_var("ATLASSIAN_API_TOKEN", "fake-token");
259
260 let cmd = ConfluenceCommand {
261 command: ConfluenceSubcommands::Move(move_page::MoveCommand {
262 id: "12345".to_string(),
263 target: "456".to_string(),
264 position: move_page::MovePosition::Append,
265 }),
266 };
267 let _ = cmd.execute().await;
268
269 std::env::remove_var("ATLASSIAN_INSTANCE_URL");
270 std::env::remove_var("ATLASSIAN_EMAIL");
271 std::env::remove_var("ATLASSIAN_API_TOKEN");
272 }
273
274 #[test]
275 fn confluence_subcommands_user_variant() {
276 let cmd = ConfluenceCommand {
277 command: ConfluenceSubcommands::User(user::UserCommand {
278 command: user::UserSubcommands::Search(user::UserSearchCommand {
279 query: "alice".to_string(),
280 limit: 25,
281 output: OutputFormat::Table,
282 }),
283 }),
284 };
285 assert!(matches!(cmd.command, ConfluenceSubcommands::User(_)));
286 }
287
288 #[test]
289 fn confluence_subcommands_space_variant() {
290 let cmd = ConfluenceCommand {
291 command: ConfluenceSubcommands::Space(space::SpaceCommand {
292 command: space::SpaceSubcommands::List(space::ListCommand {
293 keys: vec![],
294 r#type: None,
295 status: None,
296 cursor: None,
297 limit: 25,
298 output: OutputFormat::Table,
299 }),
300 }),
301 };
302 assert!(matches!(cmd.command, ConfluenceSubcommands::Space(_)));
303 }
304
305 #[test]
306 fn confluence_subcommands_space_pages_variant() {
307 let pages = ConfluenceCommand {
308 command: ConfluenceSubcommands::Space(space::SpaceCommand {
309 command: space::SpaceSubcommands::Pages(space::PagesCommand {
310 key: "ENG".to_string(),
311 status: None,
312 sort: None,
313 cursor: None,
314 limit: 25,
315 output: OutputFormat::Table,
316 }),
317 }),
318 };
319 let other = ConfluenceCommand {
320 command: ConfluenceSubcommands::Read(read::ReadCommand {
321 id: "1".to_string(),
322 out_file: None,
323 output: None,
324 format: ContentFormat::Jfm,
325 version: None,
326 }),
327 };
328 for (expected, cmd) in [(true, pages), (false, other)] {
332 assert_eq!(
333 matches!(cmd.command, ConfluenceSubcommands::Space(_)),
334 expected
335 );
336 }
337 }
338
339 #[tokio::test]
344 #[allow(clippy::await_holding_lock)]
345 async fn confluence_command_execute_space_dispatch() {
346 let _lock = crate::atlassian::auth::test_util::AUTH_ENV_MUTEX
347 .lock()
348 .unwrap_or_else(std::sync::PoisonError::into_inner);
349
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::Space(space::SpaceCommand {
356 command: space::SpaceSubcommands::List(space::ListCommand {
357 keys: vec![],
358 r#type: None,
359 status: None,
360 cursor: None,
361 limit: 25,
362 output: OutputFormat::Table,
363 }),
364 }),
365 };
366 let _ = cmd.execute().await;
367
368 std::env::remove_var("ATLASSIAN_INSTANCE_URL");
369 std::env::remove_var("ATLASSIAN_EMAIL");
370 std::env::remove_var("ATLASSIAN_API_TOKEN");
371 }
372
373 #[test]
374 fn confluence_subcommands_children_variant() {
375 let cmd = ConfluenceCommand {
376 command: ConfluenceSubcommands::Children(children::ChildrenCommand {
377 id: Some("12345".to_string()),
378 space: None,
379 recursive: false,
380 max_depth: 0,
381 output: OutputFormat::Table,
382 }),
383 };
384 assert!(matches!(cmd.command, ConfluenceSubcommands::Children(_)));
385 }
386
387 #[test]
388 fn confluence_subcommands_history_variant() {
389 let cmd = ConfluenceCommand {
390 command: ConfluenceSubcommands::History(history::HistoryCommand {
391 id: "12345".to_string(),
392 since: None,
393 limit: 20,
394 output: OutputFormat::Table,
395 }),
396 };
397 assert!(matches!(cmd.command, ConfluenceSubcommands::History(_)));
398 }
399
400 #[tokio::test]
405 #[allow(clippy::await_holding_lock)]
406 async fn confluence_command_execute_history_dispatch() {
407 let _lock = crate::atlassian::auth::test_util::AUTH_ENV_MUTEX
409 .lock()
410 .unwrap_or_else(std::sync::PoisonError::into_inner);
411
412 std::env::set_var("ATLASSIAN_INSTANCE_URL", "http://127.0.0.1:1");
413 std::env::set_var("ATLASSIAN_EMAIL", "test@example.com");
414 std::env::set_var("ATLASSIAN_API_TOKEN", "fake-token");
415
416 let cmd = ConfluenceCommand {
417 command: ConfluenceSubcommands::History(history::HistoryCommand {
418 id: "12345".to_string(),
419 since: None,
420 limit: 20,
421 output: OutputFormat::Table,
422 }),
423 };
424 let _ = cmd.execute().await;
425
426 std::env::remove_var("ATLASSIAN_INSTANCE_URL");
427 std::env::remove_var("ATLASSIAN_EMAIL");
428 std::env::remove_var("ATLASSIAN_API_TOKEN");
429 }
430
431 #[tokio::test]
436 #[allow(clippy::await_holding_lock)]
437 async fn confluence_command_execute_children_dispatch() {
438 let _lock = crate::atlassian::auth::test_util::AUTH_ENV_MUTEX
441 .lock()
442 .unwrap_or_else(std::sync::PoisonError::into_inner);
443
444 std::env::set_var("ATLASSIAN_INSTANCE_URL", "http://127.0.0.1:1");
445 std::env::set_var("ATLASSIAN_EMAIL", "test@example.com");
446 std::env::set_var("ATLASSIAN_API_TOKEN", "fake-token");
447
448 let cmd = ConfluenceCommand {
449 command: ConfluenceSubcommands::Children(children::ChildrenCommand {
450 id: Some("12345".to_string()),
451 space: None,
452 recursive: false,
453 max_depth: 0,
454 output: OutputFormat::Table,
455 }),
456 };
457 let _ = cmd.execute().await;
458
459 std::env::remove_var("ATLASSIAN_INSTANCE_URL");
460 std::env::remove_var("ATLASSIAN_EMAIL");
461 std::env::remove_var("ATLASSIAN_API_TOKEN");
462 }
463
464 #[tokio::test]
466 #[allow(clippy::await_holding_lock)]
467 async fn confluence_command_execute_attachment_dispatch() {
468 let _lock = crate::atlassian::auth::test_util::AUTH_ENV_MUTEX
470 .lock()
471 .unwrap_or_else(std::sync::PoisonError::into_inner);
472
473 std::env::set_var("ATLASSIAN_INSTANCE_URL", "http://127.0.0.1:1");
474 std::env::set_var("ATLASSIAN_EMAIL", "test@example.com");
475 std::env::set_var("ATLASSIAN_API_TOKEN", "fake-token");
476
477 let cmd = ConfluenceCommand {
478 command: ConfluenceSubcommands::Attachment(attachment::AttachmentCommand {
479 command: attachment::AttachmentSubcommands::List(attachment::ListCommand {
480 page_id: "12345".to_string(),
481 cursor: None,
482 limit: 25,
483 output: OutputFormat::Table,
484 }),
485 }),
486 };
487 let _ = cmd.execute().await;
488
489 std::env::remove_var("ATLASSIAN_INSTANCE_URL");
490 std::env::remove_var("ATLASSIAN_EMAIL");
491 std::env::remove_var("ATLASSIAN_API_TOKEN");
492 }
493
494 #[test]
495 fn confluence_subcommands_download_variant() {
496 let cmd = ConfluenceCommand {
497 command: ConfluenceSubcommands::Download(download::DownloadCommand {
498 id: Some("12345".to_string()),
499 space: None,
500 output_dir: std::path::PathBuf::from("."),
501 format: ContentFormat::Jfm,
502 concurrency: 8,
503 max_depth: 0,
504 title_filter: None,
505 resume: false,
506 include_attachments: false,
507 on_conflict: download::OnConflict::Backup,
508 }),
509 };
510 assert!(matches!(cmd.command, ConfluenceSubcommands::Download(_)));
511 }
512
513 #[test]
514 fn confluence_subcommands_download_space_variant() {
515 let cmd = ConfluenceCommand {
516 command: ConfluenceSubcommands::Download(download::DownloadCommand {
517 id: None,
518 space: Some("AD".to_string()),
519 output_dir: std::path::PathBuf::from("./AD"),
520 format: ContentFormat::Jfm,
521 concurrency: 8,
522 max_depth: 0,
523 title_filter: Some("architecture".to_string()),
524 resume: false,
525 include_attachments: false,
526 on_conflict: download::OnConflict::Backup,
527 }),
528 };
529 assert!(matches!(cmd.command, ConfluenceSubcommands::Download(_)));
530 }
531
532 #[tokio::test]
537 #[allow(clippy::await_holding_lock)]
538 async fn confluence_command_execute_compare_dispatch() {
539 let _lock = crate::atlassian::auth::test_util::AUTH_ENV_MUTEX
541 .lock()
542 .unwrap_or_else(std::sync::PoisonError::into_inner);
543
544 std::env::set_var("ATLASSIAN_INSTANCE_URL", "http://127.0.0.1:1");
545 std::env::set_var("ATLASSIAN_EMAIL", "test@example.com");
546 std::env::set_var("ATLASSIAN_API_TOKEN", "fake-token");
547
548 let cmd = ConfluenceCommand {
549 command: ConfluenceSubcommands::Compare(compare::CompareCommandGroup {
550 command: compare::CompareSubcommands::Run(compare::CompareCommand {
551 id: "12345".to_string(),
552 from: "previous".to_string(),
553 to: "latest".to_string(),
554 detail: compare::DetailArg::Outline,
555 include: "body".to_string(),
556 ignore_whitespace: true,
557 min_change_chars: 0,
558 filter_sections: Vec::new(),
559 budget: 16384,
560 output: OutputFormat::Yaml,
561 }),
562 }),
563 };
564 let _ = cmd.execute().await;
565
566 std::env::remove_var("ATLASSIAN_INSTANCE_URL");
567 std::env::remove_var("ATLASSIAN_EMAIL");
568 std::env::remove_var("ATLASSIAN_API_TOKEN");
569 }
570}