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 async fn confluence_command_execute_move_dispatch() {
235 std::env::set_var("ATLASSIAN_INSTANCE_URL", "http://127.0.0.1:1");
236 std::env::set_var("ATLASSIAN_EMAIL", "test@example.com");
237 std::env::set_var("ATLASSIAN_API_TOKEN", "fake-token");
238
239 let cmd = ConfluenceCommand {
240 command: ConfluenceSubcommands::Move(move_page::MoveCommand {
241 id: "12345".to_string(),
242 target: "456".to_string(),
243 position: move_page::MovePosition::Append,
244 }),
245 };
246 let _ = cmd.execute().await;
247
248 std::env::remove_var("ATLASSIAN_INSTANCE_URL");
249 std::env::remove_var("ATLASSIAN_EMAIL");
250 std::env::remove_var("ATLASSIAN_API_TOKEN");
251 }
252
253 #[test]
254 fn confluence_subcommands_user_variant() {
255 let cmd = ConfluenceCommand {
256 command: ConfluenceSubcommands::User(user::UserCommand {
257 command: user::UserSubcommands::Search(user::UserSearchCommand {
258 query: "alice".to_string(),
259 limit: 25,
260 output: OutputFormat::Table,
261 }),
262 }),
263 };
264 assert!(matches!(cmd.command, ConfluenceSubcommands::User(_)));
265 }
266
267 #[test]
268 fn confluence_subcommands_space_variant() {
269 let cmd = ConfluenceCommand {
270 command: ConfluenceSubcommands::Space(space::SpaceCommand {
271 command: space::SpaceSubcommands::List(space::ListCommand {
272 keys: vec![],
273 r#type: None,
274 status: None,
275 cursor: None,
276 limit: 25,
277 output: OutputFormat::Table,
278 }),
279 }),
280 };
281 assert!(matches!(cmd.command, ConfluenceSubcommands::Space(_)));
282 }
283
284 #[test]
285 fn confluence_subcommands_space_pages_variant() {
286 let pages = ConfluenceCommand {
287 command: ConfluenceSubcommands::Space(space::SpaceCommand {
288 command: space::SpaceSubcommands::Pages(space::PagesCommand {
289 key: "ENG".to_string(),
290 status: None,
291 sort: None,
292 cursor: None,
293 limit: 25,
294 output: OutputFormat::Table,
295 }),
296 }),
297 };
298 let other = ConfluenceCommand {
299 command: ConfluenceSubcommands::Read(read::ReadCommand {
300 id: "1".to_string(),
301 output: None,
302 format: ContentFormat::Jfm,
303 }),
304 };
305 for (expected, cmd) in [(true, pages), (false, other)] {
309 assert_eq!(
310 matches!(cmd.command, ConfluenceSubcommands::Space(_)),
311 expected
312 );
313 }
314 }
315
316 #[tokio::test]
321 #[allow(clippy::await_holding_lock)]
322 async fn confluence_command_execute_space_dispatch() {
323 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::Space(space::SpaceCommand {
333 command: space::SpaceSubcommands::List(space::ListCommand {
334 keys: vec![],
335 r#type: None,
336 status: None,
337 cursor: None,
338 limit: 25,
339 output: OutputFormat::Table,
340 }),
341 }),
342 };
343 let _ = cmd.execute().await;
344
345 std::env::remove_var("ATLASSIAN_INSTANCE_URL");
346 std::env::remove_var("ATLASSIAN_EMAIL");
347 std::env::remove_var("ATLASSIAN_API_TOKEN");
348 }
349
350 #[test]
351 fn confluence_subcommands_children_variant() {
352 let cmd = ConfluenceCommand {
353 command: ConfluenceSubcommands::Children(children::ChildrenCommand {
354 id: Some("12345".to_string()),
355 space: None,
356 recursive: false,
357 max_depth: 0,
358 output: OutputFormat::Table,
359 }),
360 };
361 assert!(matches!(cmd.command, ConfluenceSubcommands::Children(_)));
362 }
363
364 #[test]
365 fn confluence_subcommands_history_variant() {
366 let cmd = ConfluenceCommand {
367 command: ConfluenceSubcommands::History(history::HistoryCommand {
368 id: "12345".to_string(),
369 since: None,
370 limit: 20,
371 output: OutputFormat::Table,
372 }),
373 };
374 assert!(matches!(cmd.command, ConfluenceSubcommands::History(_)));
375 }
376
377 #[tokio::test]
382 async fn confluence_command_execute_history_dispatch() {
383 std::env::set_var("ATLASSIAN_INSTANCE_URL", "http://127.0.0.1:1");
384 std::env::set_var("ATLASSIAN_EMAIL", "test@example.com");
385 std::env::set_var("ATLASSIAN_API_TOKEN", "fake-token");
386
387 let cmd = ConfluenceCommand {
388 command: ConfluenceSubcommands::History(history::HistoryCommand {
389 id: "12345".to_string(),
390 since: None,
391 limit: 20,
392 output: OutputFormat::Table,
393 }),
394 };
395 let _ = cmd.execute().await;
396
397 std::env::remove_var("ATLASSIAN_INSTANCE_URL");
398 std::env::remove_var("ATLASSIAN_EMAIL");
399 std::env::remove_var("ATLASSIAN_API_TOKEN");
400 }
401
402 #[tokio::test]
407 #[allow(clippy::await_holding_lock)]
408 async fn confluence_command_execute_children_dispatch() {
409 let _lock = crate::atlassian::auth::test_util::AUTH_ENV_MUTEX
412 .lock()
413 .unwrap_or_else(std::sync::PoisonError::into_inner);
414
415 std::env::set_var("ATLASSIAN_INSTANCE_URL", "http://127.0.0.1:1");
416 std::env::set_var("ATLASSIAN_EMAIL", "test@example.com");
417 std::env::set_var("ATLASSIAN_API_TOKEN", "fake-token");
418
419 let cmd = ConfluenceCommand {
420 command: ConfluenceSubcommands::Children(children::ChildrenCommand {
421 id: Some("12345".to_string()),
422 space: None,
423 recursive: false,
424 max_depth: 0,
425 output: OutputFormat::Table,
426 }),
427 };
428 let _ = cmd.execute().await;
429
430 std::env::remove_var("ATLASSIAN_INSTANCE_URL");
431 std::env::remove_var("ATLASSIAN_EMAIL");
432 std::env::remove_var("ATLASSIAN_API_TOKEN");
433 }
434
435 #[tokio::test]
437 async fn confluence_command_execute_attachment_dispatch() {
438 std::env::set_var("ATLASSIAN_INSTANCE_URL", "http://127.0.0.1:1");
439 std::env::set_var("ATLASSIAN_EMAIL", "test@example.com");
440 std::env::set_var("ATLASSIAN_API_TOKEN", "fake-token");
441
442 let cmd = ConfluenceCommand {
443 command: ConfluenceSubcommands::Attachment(attachment::AttachmentCommand {
444 command: attachment::AttachmentSubcommands::List(attachment::ListCommand {
445 page_id: "12345".to_string(),
446 cursor: None,
447 limit: 25,
448 output: OutputFormat::Table,
449 }),
450 }),
451 };
452 let _ = cmd.execute().await;
453
454 std::env::remove_var("ATLASSIAN_INSTANCE_URL");
455 std::env::remove_var("ATLASSIAN_EMAIL");
456 std::env::remove_var("ATLASSIAN_API_TOKEN");
457 }
458
459 #[test]
460 fn confluence_subcommands_download_variant() {
461 let cmd = ConfluenceCommand {
462 command: ConfluenceSubcommands::Download(download::DownloadCommand {
463 id: Some("12345".to_string()),
464 space: None,
465 output_dir: std::path::PathBuf::from("."),
466 format: ContentFormat::Jfm,
467 concurrency: 8,
468 max_depth: 0,
469 title_filter: None,
470 resume: false,
471 on_conflict: download::OnConflict::Backup,
472 }),
473 };
474 assert!(matches!(cmd.command, ConfluenceSubcommands::Download(_)));
475 }
476
477 #[test]
478 fn confluence_subcommands_download_space_variant() {
479 let cmd = ConfluenceCommand {
480 command: ConfluenceSubcommands::Download(download::DownloadCommand {
481 id: None,
482 space: Some("AD".to_string()),
483 output_dir: std::path::PathBuf::from("./AD"),
484 format: ContentFormat::Jfm,
485 concurrency: 8,
486 max_depth: 0,
487 title_filter: Some("architecture".to_string()),
488 resume: false,
489 on_conflict: download::OnConflict::Backup,
490 }),
491 };
492 assert!(matches!(cmd.command, ConfluenceSubcommands::Download(_)));
493 }
494
495 #[tokio::test]
500 async fn confluence_command_execute_compare_dispatch() {
501 std::env::set_var("ATLASSIAN_INSTANCE_URL", "http://127.0.0.1:1");
502 std::env::set_var("ATLASSIAN_EMAIL", "test@example.com");
503 std::env::set_var("ATLASSIAN_API_TOKEN", "fake-token");
504
505 let cmd = ConfluenceCommand {
506 command: ConfluenceSubcommands::Compare(compare::CompareCommandGroup {
507 command: compare::CompareSubcommands::Run(compare::CompareCommand {
508 id: "12345".to_string(),
509 from: "previous".to_string(),
510 to: "latest".to_string(),
511 detail: compare::DetailArg::Outline,
512 include: "body".to_string(),
513 ignore_whitespace: true,
514 min_change_chars: 0,
515 filter_sections: Vec::new(),
516 budget: 16384,
517 output: OutputFormat::Yaml,
518 }),
519 }),
520 };
521 let _ = cmd.execute().await;
522
523 std::env::remove_var("ATLASSIAN_INSTANCE_URL");
524 std::env::remove_var("ATLASSIAN_EMAIL");
525 std::env::remove_var("ATLASSIAN_API_TOKEN");
526 }
527}