1pub(crate) mod attachment;
4pub(crate) mod children;
5pub(crate) mod comment;
6pub(crate) mod compare;
7pub(crate) mod copy;
8pub(crate) mod create;
9pub(crate) mod delete;
10pub(crate) mod download;
11pub(crate) mod edit;
12pub(crate) mod history;
13pub(crate) mod label;
14pub(crate) mod move_page;
15pub(crate) mod read;
16pub(crate) mod restriction;
17pub(crate) mod search;
18pub(crate) mod space;
19pub(crate) mod user;
20pub(crate) mod watcher;
21pub(crate) mod write;
22
23use anyhow::Result;
24use clap::{Parser, Subcommand};
25
26#[derive(Parser)]
28pub struct ConfluenceCommand {
29 #[command(subcommand)]
31 pub command: ConfluenceSubcommands,
32}
33
34#[derive(Subcommand)]
36pub enum ConfluenceSubcommands {
37 Comment(comment::CommentCommand),
39 Read(read::ReadCommand),
46 Write(write::WriteCommand),
56 Edit(edit::EditCommand),
58 Search(search::SearchCommand),
60 Create(create::CreateCommand),
62 Delete(delete::DeleteCommand),
64 Move(move_page::MoveCommand),
66 Copy(copy::CopyCommand),
68 Label(label::LabelCommand),
70 Attachment(attachment::AttachmentCommand),
72 Download(download::DownloadCommand),
74 Children(children::ChildrenCommand),
76 History(history::HistoryCommand),
78 Compare(compare::CompareCommandGroup),
80 User(user::UserCommand),
82 Space(space::SpaceCommand),
84 Watcher(watcher::WatcherCommand),
86 Restriction(restriction::RestrictionCommand),
88}
89
90impl ConfluenceCommand {
91 pub async fn execute(self) -> Result<()> {
93 match self.command {
94 ConfluenceSubcommands::Comment(cmd) => cmd.execute().await,
95 ConfluenceSubcommands::Read(cmd) => cmd.execute().await,
96 ConfluenceSubcommands::Write(cmd) => cmd.execute().await,
97 ConfluenceSubcommands::Edit(cmd) => cmd.execute().await,
98 ConfluenceSubcommands::Search(cmd) => cmd.execute().await,
99 ConfluenceSubcommands::Create(cmd) => cmd.execute().await,
100 ConfluenceSubcommands::Label(cmd) => cmd.execute().await,
101 ConfluenceSubcommands::Attachment(cmd) => cmd.execute().await,
102 ConfluenceSubcommands::Delete(cmd) => cmd.execute().await,
103 ConfluenceSubcommands::Move(cmd) => cmd.execute().await,
104 ConfluenceSubcommands::Copy(cmd) => cmd.execute().await,
105 ConfluenceSubcommands::Download(cmd) => cmd.execute().await,
106 ConfluenceSubcommands::Children(cmd) => cmd.execute().await,
107 ConfluenceSubcommands::History(cmd) => cmd.execute().await,
108 ConfluenceSubcommands::Compare(cmd) => cmd.execute().await,
109 ConfluenceSubcommands::User(cmd) => cmd.execute().await,
110 ConfluenceSubcommands::Space(cmd) => cmd.execute().await,
111 ConfluenceSubcommands::Watcher(cmd) => cmd.execute().await,
112 ConfluenceSubcommands::Restriction(cmd) => cmd.execute().await,
113 }
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120 use crate::cli::atlassian::format::{ContentFormat, OutputFormat};
121
122 #[test]
123 fn confluence_subcommands_comment_variant() {
124 let cmd = ConfluenceCommand {
125 command: ConfluenceSubcommands::Comment(comment::CommentCommand {
126 command: comment::CommentSubcommands::List(comment::ListCommand {
127 id: "12345".to_string(),
128 kind: comment::CommentKindFilter::All,
129 limit: 25,
130 output: OutputFormat::Table,
131 }),
132 }),
133 };
134 assert!(matches!(cmd.command, ConfluenceSubcommands::Comment(_)));
135 }
136
137 #[test]
138 fn confluence_subcommands_read_variant() {
139 let cmd = ConfluenceCommand {
140 command: ConfluenceSubcommands::Read(read::ReadCommand {
141 id: "12345".to_string(),
142 out_file: None,
143 output: None,
144 format: ContentFormat::Jfm,
145 version: None,
146 }),
147 };
148 assert!(matches!(cmd.command, ConfluenceSubcommands::Read(_)));
149 }
150
151 #[test]
152 fn confluence_subcommands_write_variant() {
153 let cmd = ConfluenceCommand {
154 command: ConfluenceSubcommands::Write(write::WriteCommand {
155 id: "12345".to_string(),
156 file: None,
157 format: ContentFormat::Adf,
158 force: false,
159 dry_run: false,
160 }),
161 };
162 assert!(matches!(cmd.command, ConfluenceSubcommands::Write(_)));
163 }
164
165 #[test]
166 fn confluence_subcommands_edit_variant() {
167 let cmd = ConfluenceCommand {
168 command: ConfluenceSubcommands::Edit(edit::EditCommand {
169 id: "12345".to_string(),
170 }),
171 };
172 assert!(matches!(cmd.command, ConfluenceSubcommands::Edit(_)));
173 }
174
175 #[test]
176 fn confluence_subcommands_search_variant() {
177 let cmd = ConfluenceCommand {
178 command: ConfluenceSubcommands::Search(search::SearchCommand {
179 cql: Some("space = ENG".to_string()),
180 space: None,
181 title: None,
182 limit: 25,
183 output: OutputFormat::Table,
184 }),
185 };
186 assert!(matches!(cmd.command, ConfluenceSubcommands::Search(_)));
187 }
188
189 #[test]
190 fn confluence_subcommands_create_variant() {
191 let cmd = ConfluenceCommand {
192 command: ConfluenceSubcommands::Create(create::CreateCommand {
193 file: None,
194 format: ContentFormat::Jfm,
195 space: Some("ENG".to_string()),
196 title: Some("Test".to_string()),
197 parent: None,
198 dry_run: false,
199 }),
200 };
201 assert!(matches!(cmd.command, ConfluenceSubcommands::Create(_)));
202 }
203
204 #[test]
205 fn confluence_subcommands_label_variant() {
206 let cmd = ConfluenceCommand {
207 command: ConfluenceSubcommands::Label(label::LabelCommand {
208 command: label::LabelSubcommands::List(label::ListCommand {
209 id: "12345".to_string(),
210 output: OutputFormat::Table,
211 }),
212 }),
213 };
214 assert!(matches!(cmd.command, ConfluenceSubcommands::Label(_)));
215 }
216
217 #[test]
218 fn confluence_subcommands_attachment_variant() {
219 let cmd = ConfluenceCommand {
220 command: ConfluenceSubcommands::Attachment(attachment::AttachmentCommand {
221 command: attachment::AttachmentSubcommands::List(attachment::ListCommand {
222 page_id: "12345".to_string(),
223 cursor: None,
224 limit: 25,
225 output: OutputFormat::Table,
226 }),
227 }),
228 };
229 assert!(matches!(cmd.command, ConfluenceSubcommands::Attachment(_)));
230 }
231
232 #[test]
233 fn confluence_subcommands_delete_variant() {
234 let cmd = ConfluenceCommand {
235 command: ConfluenceSubcommands::Delete(delete::DeleteCommand {
236 id: "12345".to_string(),
237 force: true,
238 dry_run: false,
239 purge: false,
240 }),
241 };
242 assert!(matches!(cmd.command, ConfluenceSubcommands::Delete(_)));
243 }
244
245 #[test]
246 fn confluence_subcommands_move_variant() {
247 let cmd = ConfluenceCommand {
248 command: ConfluenceSubcommands::Move(move_page::MoveCommand {
249 id: "12345".to_string(),
250 target: "456".to_string(),
251 position: move_page::MovePosition::Append,
252 }),
253 };
254 assert!(matches!(cmd.command, ConfluenceSubcommands::Move(_)));
255 }
256
257 #[tokio::test]
261 #[allow(clippy::await_holding_lock)]
262 async fn confluence_command_execute_move_dispatch() {
263 let _lock = crate::atlassian::auth::test_util::AUTH_ENV_MUTEX
265 .lock()
266 .unwrap_or_else(std::sync::PoisonError::into_inner);
267
268 std::env::set_var("ATLASSIAN_INSTANCE_URL", "http://127.0.0.1:1");
269 std::env::set_var("ATLASSIAN_EMAIL", "test@example.com");
270 std::env::set_var("ATLASSIAN_API_TOKEN", "fake-token");
271
272 let cmd = ConfluenceCommand {
273 command: ConfluenceSubcommands::Move(move_page::MoveCommand {
274 id: "12345".to_string(),
275 target: "456".to_string(),
276 position: move_page::MovePosition::Append,
277 }),
278 };
279 let _ = cmd.execute().await;
280
281 std::env::remove_var("ATLASSIAN_INSTANCE_URL");
282 std::env::remove_var("ATLASSIAN_EMAIL");
283 std::env::remove_var("ATLASSIAN_API_TOKEN");
284 }
285
286 #[test]
287 fn confluence_subcommands_user_variant() {
288 let cmd = ConfluenceCommand {
289 command: ConfluenceSubcommands::User(user::UserCommand {
290 command: user::UserSubcommands::Search(user::UserSearchCommand {
291 query: "alice".to_string(),
292 limit: 25,
293 output: OutputFormat::Table,
294 }),
295 }),
296 };
297 assert!(matches!(cmd.command, ConfluenceSubcommands::User(_)));
298 }
299
300 #[test]
301 fn confluence_subcommands_space_variant() {
302 let cmd = ConfluenceCommand {
303 command: ConfluenceSubcommands::Space(space::SpaceCommand {
304 command: space::SpaceSubcommands::List(space::ListCommand {
305 keys: vec![],
306 r#type: None,
307 status: None,
308 cursor: None,
309 limit: 25,
310 output: OutputFormat::Table,
311 }),
312 }),
313 };
314 assert!(matches!(cmd.command, ConfluenceSubcommands::Space(_)));
315 }
316
317 #[test]
318 fn confluence_subcommands_space_pages_variant() {
319 let pages = ConfluenceCommand {
320 command: ConfluenceSubcommands::Space(space::SpaceCommand {
321 command: space::SpaceSubcommands::Pages(space::PagesCommand {
322 key: "ENG".to_string(),
323 status: None,
324 sort: None,
325 cursor: None,
326 limit: 25,
327 output: OutputFormat::Table,
328 }),
329 }),
330 };
331 let other = ConfluenceCommand {
332 command: ConfluenceSubcommands::Read(read::ReadCommand {
333 id: "1".to_string(),
334 out_file: None,
335 output: None,
336 format: ContentFormat::Jfm,
337 version: None,
338 }),
339 };
340 for (expected, cmd) in [(true, pages), (false, other)] {
344 assert_eq!(
345 matches!(cmd.command, ConfluenceSubcommands::Space(_)),
346 expected
347 );
348 }
349 }
350
351 #[tokio::test]
356 #[allow(clippy::await_holding_lock)]
357 async fn confluence_command_execute_space_dispatch() {
358 let _lock = crate::atlassian::auth::test_util::AUTH_ENV_MUTEX
359 .lock()
360 .unwrap_or_else(std::sync::PoisonError::into_inner);
361
362 std::env::set_var("ATLASSIAN_INSTANCE_URL", "http://127.0.0.1:1");
363 std::env::set_var("ATLASSIAN_EMAIL", "test@example.com");
364 std::env::set_var("ATLASSIAN_API_TOKEN", "fake-token");
365
366 let cmd = ConfluenceCommand {
367 command: ConfluenceSubcommands::Space(space::SpaceCommand {
368 command: space::SpaceSubcommands::List(space::ListCommand {
369 keys: vec![],
370 r#type: None,
371 status: None,
372 cursor: None,
373 limit: 25,
374 output: OutputFormat::Table,
375 }),
376 }),
377 };
378 let _ = cmd.execute().await;
379
380 std::env::remove_var("ATLASSIAN_INSTANCE_URL");
381 std::env::remove_var("ATLASSIAN_EMAIL");
382 std::env::remove_var("ATLASSIAN_API_TOKEN");
383 }
384
385 #[test]
386 fn confluence_subcommands_children_variant() {
387 let cmd = ConfluenceCommand {
388 command: ConfluenceSubcommands::Children(children::ChildrenCommand {
389 id: Some("12345".to_string()),
390 space: None,
391 recursive: false,
392 max_depth: 0,
393 output: OutputFormat::Table,
394 }),
395 };
396 assert!(matches!(cmd.command, ConfluenceSubcommands::Children(_)));
397 }
398
399 #[test]
400 fn confluence_subcommands_history_variant() {
401 let cmd = ConfluenceCommand {
402 command: ConfluenceSubcommands::History(history::HistoryCommand {
403 id: "12345".to_string(),
404 since: None,
405 limit: 20,
406 output: OutputFormat::Table,
407 }),
408 };
409 assert!(matches!(cmd.command, ConfluenceSubcommands::History(_)));
410 }
411
412 #[tokio::test]
417 #[allow(clippy::await_holding_lock)]
418 async fn confluence_command_execute_history_dispatch() {
419 let _lock = crate::atlassian::auth::test_util::AUTH_ENV_MUTEX
421 .lock()
422 .unwrap_or_else(std::sync::PoisonError::into_inner);
423
424 std::env::set_var("ATLASSIAN_INSTANCE_URL", "http://127.0.0.1:1");
425 std::env::set_var("ATLASSIAN_EMAIL", "test@example.com");
426 std::env::set_var("ATLASSIAN_API_TOKEN", "fake-token");
427
428 let cmd = ConfluenceCommand {
429 command: ConfluenceSubcommands::History(history::HistoryCommand {
430 id: "12345".to_string(),
431 since: None,
432 limit: 20,
433 output: OutputFormat::Table,
434 }),
435 };
436 let _ = cmd.execute().await;
437
438 std::env::remove_var("ATLASSIAN_INSTANCE_URL");
439 std::env::remove_var("ATLASSIAN_EMAIL");
440 std::env::remove_var("ATLASSIAN_API_TOKEN");
441 }
442
443 #[tokio::test]
448 #[allow(clippy::await_holding_lock)]
449 async fn confluence_command_execute_children_dispatch() {
450 let _lock = crate::atlassian::auth::test_util::AUTH_ENV_MUTEX
453 .lock()
454 .unwrap_or_else(std::sync::PoisonError::into_inner);
455
456 std::env::set_var("ATLASSIAN_INSTANCE_URL", "http://127.0.0.1:1");
457 std::env::set_var("ATLASSIAN_EMAIL", "test@example.com");
458 std::env::set_var("ATLASSIAN_API_TOKEN", "fake-token");
459
460 let cmd = ConfluenceCommand {
461 command: ConfluenceSubcommands::Children(children::ChildrenCommand {
462 id: Some("12345".to_string()),
463 space: None,
464 recursive: false,
465 max_depth: 0,
466 output: OutputFormat::Table,
467 }),
468 };
469 let _ = cmd.execute().await;
470
471 std::env::remove_var("ATLASSIAN_INSTANCE_URL");
472 std::env::remove_var("ATLASSIAN_EMAIL");
473 std::env::remove_var("ATLASSIAN_API_TOKEN");
474 }
475
476 #[tokio::test]
478 #[allow(clippy::await_holding_lock)]
479 async fn confluence_command_execute_attachment_dispatch() {
480 let _lock = crate::atlassian::auth::test_util::AUTH_ENV_MUTEX
482 .lock()
483 .unwrap_or_else(std::sync::PoisonError::into_inner);
484
485 std::env::set_var("ATLASSIAN_INSTANCE_URL", "http://127.0.0.1:1");
486 std::env::set_var("ATLASSIAN_EMAIL", "test@example.com");
487 std::env::set_var("ATLASSIAN_API_TOKEN", "fake-token");
488
489 let cmd = ConfluenceCommand {
490 command: ConfluenceSubcommands::Attachment(attachment::AttachmentCommand {
491 command: attachment::AttachmentSubcommands::List(attachment::ListCommand {
492 page_id: "12345".to_string(),
493 cursor: None,
494 limit: 25,
495 output: OutputFormat::Table,
496 }),
497 }),
498 };
499 let _ = cmd.execute().await;
500
501 std::env::remove_var("ATLASSIAN_INSTANCE_URL");
502 std::env::remove_var("ATLASSIAN_EMAIL");
503 std::env::remove_var("ATLASSIAN_API_TOKEN");
504 }
505
506 #[test]
507 fn confluence_subcommands_download_variant() {
508 let cmd = ConfluenceCommand {
509 command: ConfluenceSubcommands::Download(download::DownloadCommand {
510 id: Some("12345".to_string()),
511 space: None,
512 output_dir: std::path::PathBuf::from("."),
513 format: ContentFormat::Jfm,
514 concurrency: 8,
515 max_depth: 0,
516 title_filter: None,
517 resume: false,
518 include_attachments: false,
519 on_conflict: download::OnConflict::Backup,
520 }),
521 };
522 assert!(matches!(cmd.command, ConfluenceSubcommands::Download(_)));
523 }
524
525 #[test]
526 fn confluence_subcommands_download_space_variant() {
527 let cmd = ConfluenceCommand {
528 command: ConfluenceSubcommands::Download(download::DownloadCommand {
529 id: None,
530 space: Some("AD".to_string()),
531 output_dir: std::path::PathBuf::from("./AD"),
532 format: ContentFormat::Jfm,
533 concurrency: 8,
534 max_depth: 0,
535 title_filter: Some("architecture".to_string()),
536 resume: false,
537 include_attachments: false,
538 on_conflict: download::OnConflict::Backup,
539 }),
540 };
541 assert!(matches!(cmd.command, ConfluenceSubcommands::Download(_)));
542 }
543
544 #[tokio::test]
549 #[allow(clippy::await_holding_lock)]
550 async fn confluence_command_execute_compare_dispatch() {
551 let _lock = crate::atlassian::auth::test_util::AUTH_ENV_MUTEX
553 .lock()
554 .unwrap_or_else(std::sync::PoisonError::into_inner);
555
556 std::env::set_var("ATLASSIAN_INSTANCE_URL", "http://127.0.0.1:1");
557 std::env::set_var("ATLASSIAN_EMAIL", "test@example.com");
558 std::env::set_var("ATLASSIAN_API_TOKEN", "fake-token");
559
560 let cmd = ConfluenceCommand {
561 command: ConfluenceSubcommands::Compare(compare::CompareCommandGroup {
562 command: compare::CompareSubcommands::Run(compare::CompareCommand {
563 id: "12345".to_string(),
564 from: "previous".to_string(),
565 to: "latest".to_string(),
566 detail: compare::DetailArg::Outline,
567 include: "body".to_string(),
568 ignore_whitespace: true,
569 min_change_chars: 0,
570 filter_sections: Vec::new(),
571 budget: 16384,
572 output: OutputFormat::Yaml,
573 }),
574 }),
575 };
576 let _ = cmd.execute().await;
577
578 std::env::remove_var("ATLASSIAN_INSTANCE_URL");
579 std::env::remove_var("ATLASSIAN_EMAIL");
580 std::env::remove_var("ATLASSIAN_API_TOKEN");
581 }
582
583 fn set_dead_credentials() {
589 std::env::set_var("ATLASSIAN_INSTANCE_URL", "http://127.0.0.1:1");
590 std::env::set_var("ATLASSIAN_EMAIL", "test@example.com");
591 std::env::set_var("ATLASSIAN_API_TOKEN", "fake-token");
592 }
593
594 fn clear_credentials_env() {
595 std::env::remove_var("ATLASSIAN_INSTANCE_URL");
596 std::env::remove_var("ATLASSIAN_EMAIL");
597 std::env::remove_var("ATLASSIAN_API_TOKEN");
598 }
599
600 #[tokio::test]
602 #[allow(clippy::await_holding_lock)]
603 async fn confluence_command_execute_copy_dispatch() {
604 let _lock = crate::atlassian::auth::test_util::AUTH_ENV_MUTEX
605 .lock()
606 .unwrap_or_else(std::sync::PoisonError::into_inner);
607 set_dead_credentials();
608
609 let cmd = ConfluenceCommand {
610 command: ConfluenceSubcommands::Copy(copy::CopyCommand {
611 id: "12345".to_string(),
612 parent: "67890".to_string(),
613 title: "Copy".to_string(),
614 }),
615 };
616 let _ = cmd.execute().await;
617
618 clear_credentials_env();
619 }
620
621 #[tokio::test]
623 #[allow(clippy::await_holding_lock)]
624 async fn confluence_command_execute_watcher_dispatch() {
625 let _lock = crate::atlassian::auth::test_util::AUTH_ENV_MUTEX
626 .lock()
627 .unwrap_or_else(std::sync::PoisonError::into_inner);
628 set_dead_credentials();
629
630 let cmd = ConfluenceCommand {
631 command: ConfluenceSubcommands::Watcher(watcher::WatcherCommand {
632 command: watcher::WatcherSubcommands::Status(watcher::WatchArgs {
633 id: "12345".to_string(),
634 account_id: None,
635 }),
636 }),
637 };
638 let _ = cmd.execute().await;
639
640 clear_credentials_env();
641 }
642
643 #[tokio::test]
645 #[allow(clippy::await_holding_lock)]
646 async fn confluence_command_execute_restriction_dispatch() {
647 let _lock = crate::atlassian::auth::test_util::AUTH_ENV_MUTEX
648 .lock()
649 .unwrap_or_else(std::sync::PoisonError::into_inner);
650 set_dead_credentials();
651
652 let cmd = ConfluenceCommand {
653 command: ConfluenceSubcommands::Restriction(restriction::RestrictionCommand {
654 command: restriction::RestrictionSubcommands::Get(restriction::GetCommand {
655 id: "12345".to_string(),
656 }),
657 }),
658 };
659 let _ = cmd.execute().await;
660
661 clear_credentials_env();
662 }
663}