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