Skip to main content

omni_dev/cli/atlassian/confluence/
mod.rs

1//! Confluence CLI subcommands.
2
3pub(crate) mod children;
4pub(crate) mod comment;
5pub(crate) mod create;
6pub(crate) mod delete;
7pub(crate) mod download;
8pub(crate) mod edit;
9pub(crate) mod label;
10pub(crate) mod read;
11pub(crate) mod search;
12pub(crate) mod user;
13pub(crate) mod write;
14
15use anyhow::Result;
16use clap::{Parser, Subcommand};
17
18/// Confluence page management, search, and more.
19#[derive(Parser)]
20pub struct ConfluenceCommand {
21    /// The Confluence subcommand to execute.
22    #[command(subcommand)]
23    pub command: ConfluenceSubcommands,
24}
25
26/// Confluence subcommands.
27#[derive(Subcommand)]
28pub enum ConfluenceSubcommands {
29    /// Manages comments on a Confluence page.
30    Comment(comment::CommentCommand),
31    /// Fetches a Confluence page and outputs it as JFM markdown or ADF JSON.
32    Read(read::ReadCommand),
33    /// Pushes content to a Confluence page.
34    Write(write::WriteCommand),
35    /// Interactive fetch-edit-push cycle for a Confluence page.
36    Edit(edit::EditCommand),
37    /// Searches Confluence pages using CQL.
38    Search(search::SearchCommand),
39    /// Creates a new Confluence page.
40    Create(create::CreateCommand),
41    /// Deletes a Confluence page.
42    Delete(delete::DeleteCommand),
43    /// Manages labels on Confluence pages.
44    Label(label::LabelCommand),
45    /// Recursively downloads a Confluence page tree.
46    Download(download::DownloadCommand),
47    /// Lists child pages of a Confluence page or top-level pages in a space.
48    Children(children::ChildrenCommand),
49    /// Confluence user operations.
50    User(user::UserCommand),
51}
52
53impl ConfluenceCommand {
54    /// Executes the Confluence command.
55    pub async fn execute(self) -> Result<()> {
56        match self.command {
57            ConfluenceSubcommands::Comment(cmd) => cmd.execute().await,
58            ConfluenceSubcommands::Read(cmd) => cmd.execute().await,
59            ConfluenceSubcommands::Write(cmd) => cmd.execute().await,
60            ConfluenceSubcommands::Edit(cmd) => cmd.execute().await,
61            ConfluenceSubcommands::Search(cmd) => cmd.execute().await,
62            ConfluenceSubcommands::Create(cmd) => cmd.execute().await,
63            ConfluenceSubcommands::Label(cmd) => cmd.execute().await,
64            ConfluenceSubcommands::Delete(cmd) => cmd.execute().await,
65            ConfluenceSubcommands::Download(cmd) => cmd.execute().await,
66            ConfluenceSubcommands::Children(cmd) => cmd.execute().await,
67            ConfluenceSubcommands::User(cmd) => cmd.execute().await,
68        }
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75    use crate::cli::atlassian::format::{ContentFormat, OutputFormat};
76
77    #[test]
78    fn confluence_subcommands_comment_variant() {
79        let cmd = ConfluenceCommand {
80            command: ConfluenceSubcommands::Comment(comment::CommentCommand {
81                command: comment::CommentSubcommands::List(comment::ListCommand {
82                    id: "12345".to_string(),
83                    limit: 25,
84                    output: OutputFormat::Table,
85                }),
86            }),
87        };
88        assert!(matches!(cmd.command, ConfluenceSubcommands::Comment(_)));
89    }
90
91    #[test]
92    fn confluence_subcommands_read_variant() {
93        let cmd = ConfluenceCommand {
94            command: ConfluenceSubcommands::Read(read::ReadCommand {
95                id: "12345".to_string(),
96                output: None,
97                format: ContentFormat::Jfm,
98            }),
99        };
100        assert!(matches!(cmd.command, ConfluenceSubcommands::Read(_)));
101    }
102
103    #[test]
104    fn confluence_subcommands_write_variant() {
105        let cmd = ConfluenceCommand {
106            command: ConfluenceSubcommands::Write(write::WriteCommand {
107                id: "12345".to_string(),
108                file: None,
109                format: ContentFormat::Adf,
110                force: false,
111                dry_run: false,
112            }),
113        };
114        assert!(matches!(cmd.command, ConfluenceSubcommands::Write(_)));
115    }
116
117    #[test]
118    fn confluence_subcommands_edit_variant() {
119        let cmd = ConfluenceCommand {
120            command: ConfluenceSubcommands::Edit(edit::EditCommand {
121                id: "12345".to_string(),
122            }),
123        };
124        assert!(matches!(cmd.command, ConfluenceSubcommands::Edit(_)));
125    }
126
127    #[test]
128    fn confluence_subcommands_search_variant() {
129        let cmd = ConfluenceCommand {
130            command: ConfluenceSubcommands::Search(search::SearchCommand {
131                cql: Some("space = ENG".to_string()),
132                space: None,
133                title: None,
134                limit: 25,
135                output: OutputFormat::Table,
136            }),
137        };
138        assert!(matches!(cmd.command, ConfluenceSubcommands::Search(_)));
139    }
140
141    #[test]
142    fn confluence_subcommands_create_variant() {
143        let cmd = ConfluenceCommand {
144            command: ConfluenceSubcommands::Create(create::CreateCommand {
145                file: None,
146                format: ContentFormat::Jfm,
147                space: Some("ENG".to_string()),
148                title: Some("Test".to_string()),
149                parent: None,
150                dry_run: false,
151            }),
152        };
153        assert!(matches!(cmd.command, ConfluenceSubcommands::Create(_)));
154    }
155
156    #[test]
157    fn confluence_subcommands_label_variant() {
158        let cmd = ConfluenceCommand {
159            command: ConfluenceSubcommands::Label(label::LabelCommand {
160                command: label::LabelSubcommands::List(label::ListCommand {
161                    id: "12345".to_string(),
162                    output: OutputFormat::Table,
163                }),
164            }),
165        };
166        assert!(matches!(cmd.command, ConfluenceSubcommands::Label(_)));
167    }
168
169    #[test]
170    fn confluence_subcommands_delete_variant() {
171        let cmd = ConfluenceCommand {
172            command: ConfluenceSubcommands::Delete(delete::DeleteCommand {
173                id: "12345".to_string(),
174                force: true,
175                purge: false,
176            }),
177        };
178        assert!(matches!(cmd.command, ConfluenceSubcommands::Delete(_)));
179    }
180
181    #[test]
182    fn confluence_subcommands_user_variant() {
183        let cmd = ConfluenceCommand {
184            command: ConfluenceSubcommands::User(user::UserCommand {
185                command: user::UserSubcommands::Search(user::UserSearchCommand {
186                    query: "alice".to_string(),
187                    limit: 25,
188                    output: OutputFormat::Table,
189                }),
190            }),
191        };
192        assert!(matches!(cmd.command, ConfluenceSubcommands::User(_)));
193    }
194
195    #[test]
196    fn confluence_subcommands_children_variant() {
197        let cmd = ConfluenceCommand {
198            command: ConfluenceSubcommands::Children(children::ChildrenCommand {
199                id: Some("12345".to_string()),
200                space: None,
201                recursive: false,
202                max_depth: 0,
203                output: OutputFormat::Table,
204            }),
205        };
206        assert!(matches!(cmd.command, ConfluenceSubcommands::Children(_)));
207    }
208
209    /// Exercises the `Children` dispatch arm in `ConfluenceCommand::execute`
210    /// with injected fake credentials so `create_client()` succeeds and the
211    /// downstream call is reached. The subsequent API call is allowed to
212    /// fail — we only care that the dispatch line runs.
213    #[tokio::test]
214    async fn confluence_command_execute_children_dispatch() {
215        std::env::set_var("ATLASSIAN_INSTANCE_URL", "http://127.0.0.1:1");
216        std::env::set_var("ATLASSIAN_EMAIL", "test@example.com");
217        std::env::set_var("ATLASSIAN_API_TOKEN", "fake-token");
218
219        let cmd = ConfluenceCommand {
220            command: ConfluenceSubcommands::Children(children::ChildrenCommand {
221                id: Some("12345".to_string()),
222                space: None,
223                recursive: false,
224                max_depth: 0,
225                output: OutputFormat::Table,
226            }),
227        };
228        let _ = cmd.execute().await;
229
230        std::env::remove_var("ATLASSIAN_INSTANCE_URL");
231        std::env::remove_var("ATLASSIAN_EMAIL");
232        std::env::remove_var("ATLASSIAN_API_TOKEN");
233    }
234
235    #[test]
236    fn confluence_subcommands_download_variant() {
237        let cmd = ConfluenceCommand {
238            command: ConfluenceSubcommands::Download(download::DownloadCommand {
239                id: Some("12345".to_string()),
240                space: None,
241                output_dir: std::path::PathBuf::from("."),
242                format: ContentFormat::Jfm,
243                concurrency: 8,
244                max_depth: 0,
245                title_filter: None,
246                resume: false,
247                on_conflict: download::OnConflict::Backup,
248            }),
249        };
250        assert!(matches!(cmd.command, ConfluenceSubcommands::Download(_)));
251    }
252
253    #[test]
254    fn confluence_subcommands_download_space_variant() {
255        let cmd = ConfluenceCommand {
256            command: ConfluenceSubcommands::Download(download::DownloadCommand {
257                id: None,
258                space: Some("AD".to_string()),
259                output_dir: std::path::PathBuf::from("./AD"),
260                format: ContentFormat::Jfm,
261                concurrency: 8,
262                max_depth: 0,
263                title_filter: Some("architecture".to_string()),
264                resume: false,
265                on_conflict: download::OnConflict::Backup,
266            }),
267        };
268        assert!(matches!(cmd.command, ConfluenceSubcommands::Download(_)));
269    }
270}