1pub(crate) mod attachment;
4pub(crate) mod board;
5pub(crate) mod changelog;
6pub(crate) mod comment;
7pub(crate) mod component;
8pub(crate) mod create;
9pub(crate) mod delete;
10pub(crate) mod dev;
11pub(crate) mod edit;
12pub(crate) mod field;
13pub(crate) mod label;
14pub(crate) mod link;
15pub(crate) mod project;
16pub(crate) mod read;
17pub(crate) mod search;
18pub(crate) mod sprint;
19pub(crate) mod transition;
20pub(crate) mod user;
21pub(crate) mod version;
22pub(crate) mod watcher;
23pub(crate) mod worklog;
24pub(crate) mod write;
25
26use anyhow::Result;
27use clap::{Parser, Subcommand};
28
29#[derive(Parser)]
31pub struct JiraCommand {
32 #[command(subcommand)]
34 pub command: JiraSubcommands,
35}
36
37#[derive(Subcommand)]
39pub enum JiraSubcommands {
40 Read(read::ReadCommand),
42 Write(write::WriteCommand),
44 Edit(edit::EditCommand),
46 Search(search::SearchCommand),
48 Create(create::CreateCommand),
50 Transition(transition::TransitionCommand),
52 Comment(comment::CommentCommand),
54 Delete(delete::DeleteCommand),
56 Dev(dev::DevCommand),
58 Project(project::ProjectCommand),
60 Field(field::FieldCommand),
62 Board(board::BoardCommand),
64 Sprint(sprint::SprintCommand),
66 Link(link::LinkCommand),
68 Label(label::LabelCommand),
70 Changelog(changelog::ChangelogCommand),
72 Attachment(attachment::AttachmentCommand),
74 Watcher(watcher::WatcherCommand),
76 Worklog(worklog::WorklogCommand),
78 User(user::UserCommand),
80 Version(version::VersionCommand),
82 Component(component::ComponentCommand),
84}
85
86impl JiraCommand {
87 pub async fn execute(self) -> Result<()> {
89 match self.command {
90 JiraSubcommands::Read(cmd) => cmd.execute().await,
91 JiraSubcommands::Write(cmd) => cmd.execute().await,
92 JiraSubcommands::Edit(cmd) => cmd.execute().await,
93 JiraSubcommands::Search(cmd) => cmd.execute().await,
94 JiraSubcommands::Create(cmd) => cmd.execute().await,
95 JiraSubcommands::Transition(cmd) => cmd.execute().await,
96 JiraSubcommands::Comment(cmd) => cmd.execute().await,
97 JiraSubcommands::Delete(cmd) => cmd.execute().await,
98 JiraSubcommands::Dev(cmd) => cmd.execute().await,
99 JiraSubcommands::Project(cmd) => cmd.execute().await,
100 JiraSubcommands::Field(cmd) => cmd.execute().await,
101 JiraSubcommands::Board(cmd) => cmd.execute().await,
102 JiraSubcommands::Sprint(cmd) => cmd.execute().await,
103 JiraSubcommands::Link(cmd) => cmd.execute().await,
104 JiraSubcommands::Label(cmd) => cmd.execute().await,
105 JiraSubcommands::Changelog(cmd) => cmd.execute().await,
106 JiraSubcommands::Attachment(cmd) => cmd.execute().await,
107 JiraSubcommands::Watcher(cmd) => cmd.execute().await,
108 JiraSubcommands::Worklog(cmd) => cmd.execute().await,
109 JiraSubcommands::User(cmd) => cmd.execute().await,
110 JiraSubcommands::Version(cmd) => cmd.execute().await,
111 JiraSubcommands::Component(cmd) => cmd.execute().await,
112 }
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119 use crate::cli::atlassian::format::{ContentFormat, OutputFormat};
120
121 #[test]
122 fn jira_subcommands_read_variant() {
123 let cmd = JiraCommand {
124 command: JiraSubcommands::Read(read::ReadCommand {
125 key: "PROJ-1".to_string(),
126 out_file: None,
127 output: None,
128 format: ContentFormat::Jfm,
129 fields: vec![],
130 all_fields: false,
131 }),
132 };
133 assert!(matches!(cmd.command, JiraSubcommands::Read(_)));
134 }
135
136 #[test]
137 fn jira_subcommands_write_variant() {
138 let cmd = JiraCommand {
139 command: JiraSubcommands::Write(write::WriteCommand {
140 key: "PROJ-1".to_string(),
141 file: None,
142 format: ContentFormat::Jfm,
143 no_content: false,
144 assignee: None,
145 reporter: None,
146 set_fields: vec![],
147 force: false,
148 dry_run: false,
149 }),
150 };
151 assert!(matches!(cmd.command, JiraSubcommands::Write(_)));
152 }
153
154 #[test]
155 fn jira_subcommands_edit_variant() {
156 let cmd = JiraCommand {
157 command: JiraSubcommands::Edit(edit::EditCommand {
158 key: "PROJ-1".to_string(),
159 }),
160 };
161 assert!(matches!(cmd.command, JiraSubcommands::Edit(_)));
162 }
163
164 #[test]
165 fn jira_subcommands_create_variant() {
166 let cmd = JiraCommand {
167 command: JiraSubcommands::Create(create::CreateCommand {
168 file: None,
169 format: ContentFormat::Jfm,
170 project: Some("PROJ".to_string()),
171 r#type: None,
172 summary: Some("Test".to_string()),
173 set_fields: vec![],
174 dry_run: false,
175 }),
176 };
177 assert!(matches!(cmd.command, JiraSubcommands::Create(_)));
178 }
179
180 #[test]
181 fn jira_subcommands_search_variant() {
182 let cmd = JiraCommand {
183 command: JiraSubcommands::Search(search::SearchCommand {
184 jql: Some("project = PROJ".to_string()),
185 project: None,
186 assignee: None,
187 status: None,
188 limit: 50,
189 output: OutputFormat::Table,
190 }),
191 };
192 assert!(matches!(cmd.command, JiraSubcommands::Search(_)));
193 }
194
195 #[test]
196 fn jira_subcommands_transition_variant() {
197 let cmd = JiraCommand {
198 command: JiraSubcommands::Transition(transition::TransitionCommand {
199 command: transition::TransitionSubcommands::Execute(transition::ExecuteCommand {
200 key: "PROJ-1".to_string(),
201 transition: "Done".to_string(),
202 set_fields: vec![],
203 resolution: None,
204 comment: None,
205 }),
206 }),
207 };
208 assert!(matches!(cmd.command, JiraSubcommands::Transition(_)));
209 }
210
211 #[test]
212 fn jira_subcommands_comment_variant() {
213 let cmd = JiraCommand {
214 command: JiraSubcommands::Comment(comment::CommentCommand {
215 command: comment::CommentSubcommands::List(comment::ListCommand {
216 key: "PROJ-1".to_string(),
217 output: OutputFormat::Table,
218 limit: 0,
219 }),
220 }),
221 };
222 assert!(matches!(cmd.command, JiraSubcommands::Comment(_)));
223 }
224
225 #[test]
226 fn jira_subcommands_delete_variant() {
227 let cmd = JiraCommand {
228 command: JiraSubcommands::Delete(delete::DeleteCommand {
229 key: "PROJ-1".to_string(),
230 force: true,
231 dry_run: false,
232 }),
233 };
234 assert!(matches!(cmd.command, JiraSubcommands::Delete(_)));
235 }
236
237 #[test]
238 fn jira_subcommands_dev_variant() {
239 let cmd = JiraCommand {
240 command: JiraSubcommands::Dev(dev::DevCommand {
241 key: "PROJ-1".to_string(),
242 r#type: None,
243 app: None,
244 summary: false,
245 output: OutputFormat::Table,
246 }),
247 };
248 assert!(matches!(cmd.command, JiraSubcommands::Dev(_)));
249 }
250
251 #[test]
252 fn jira_subcommands_project_variant() {
253 let cmd = JiraCommand {
254 command: JiraSubcommands::Project(project::ProjectCommand {
255 command: project::ProjectSubcommands::List(project::ListCommand {
256 limit: 50,
257 output: OutputFormat::Table,
258 }),
259 }),
260 };
261 assert!(matches!(cmd.command, JiraSubcommands::Project(_)));
262 }
263
264 #[test]
265 fn jira_subcommands_field_variant() {
266 let cmd = JiraCommand {
267 command: JiraSubcommands::Field(field::FieldCommand {
268 command: field::FieldSubcommands::List(field::ListCommand {
269 search: None,
270 output: OutputFormat::Table,
271 }),
272 }),
273 };
274 assert!(matches!(cmd.command, JiraSubcommands::Field(_)));
275 }
276
277 #[test]
278 fn jira_subcommands_board_variant() {
279 let cmd = JiraCommand {
280 command: JiraSubcommands::Board(board::BoardCommand {
281 command: board::BoardSubcommands::List(board::ListCommand {
282 project: None,
283 r#type: None,
284 limit: 50,
285 output: OutputFormat::Table,
286 }),
287 }),
288 };
289 assert!(matches!(cmd.command, JiraSubcommands::Board(_)));
290 }
291
292 #[test]
293 fn jira_subcommands_sprint_variant() {
294 let cmd = JiraCommand {
295 command: JiraSubcommands::Sprint(sprint::SprintCommand {
296 command: sprint::SprintSubcommands::List(sprint::ListCommand {
297 board_id: 1,
298 state: None,
299 limit: 50,
300 output: OutputFormat::Table,
301 }),
302 }),
303 };
304 assert!(matches!(cmd.command, JiraSubcommands::Sprint(_)));
305 }
306
307 #[test]
308 fn jira_subcommands_link_variant() {
309 let cmd = JiraCommand {
310 command: JiraSubcommands::Link(link::LinkCommand {
311 command: link::LinkSubcommands::Types(link::TypesCommand {
312 output: OutputFormat::Table,
313 }),
314 }),
315 };
316 assert!(matches!(cmd.command, JiraSubcommands::Link(_)));
317 }
318
319 #[test]
320 fn jira_subcommands_changelog_variant() {
321 let cmd = JiraCommand {
322 command: JiraSubcommands::Changelog(changelog::ChangelogCommand {
323 keys: "PROJ-1".to_string(),
324 limit: 50,
325 output: OutputFormat::Table,
326 }),
327 };
328 assert!(matches!(cmd.command, JiraSubcommands::Changelog(_)));
329 }
330
331 #[test]
332 fn jira_subcommands_attachment_variant() {
333 let cmd = JiraCommand {
334 command: JiraSubcommands::Attachment(attachment::AttachmentCommand {
335 command: attachment::AttachmentSubcommands::Download(attachment::DownloadCommand {
336 key: "PROJ-1".to_string(),
337 output_dir: ".".to_string(),
338 filter: None,
339 }),
340 }),
341 };
342 assert!(matches!(cmd.command, JiraSubcommands::Attachment(_)));
343 }
344
345 #[test]
346 fn jira_subcommands_watcher_variant() {
347 let cmd = JiraCommand {
348 command: JiraSubcommands::Watcher(watcher::WatcherCommand {
349 command: watcher::WatcherSubcommands::List(watcher::ListCommand {
350 key: "PROJ-1".to_string(),
351 output: OutputFormat::Table,
352 }),
353 }),
354 };
355 assert!(matches!(cmd.command, JiraSubcommands::Watcher(_)));
356 }
357
358 #[test]
359 fn jira_subcommands_worklog_variant() {
360 let cmd = JiraCommand {
361 command: JiraSubcommands::Worklog(worklog::WorklogCommand {
362 command: worklog::WorklogSubcommands::List(worklog::ListCommand {
363 key: "PROJ-1".to_string(),
364 limit: 50,
365 output: OutputFormat::Table,
366 }),
367 }),
368 };
369 assert!(matches!(cmd.command, JiraSubcommands::Worklog(_)));
370 }
371
372 #[test]
373 fn jira_subcommands_user_variant() {
374 let cmd = JiraCommand {
375 command: JiraSubcommands::User(user::UserCommand {
376 command: user::UserSubcommands::Search(user::UserSearchCommand {
377 query: "alice".to_string(),
378 limit: 25,
379 output: OutputFormat::Table,
380 }),
381 }),
382 };
383 assert!(matches!(cmd.command, JiraSubcommands::User(_)));
384 }
385
386 #[test]
387 fn jira_subcommands_version_variant() {
388 let cmd = JiraCommand {
389 command: JiraSubcommands::Version(version::VersionCommand {
390 command: version::VersionSubcommands::List(version::ListCommand {
391 project: "PROJ".to_string(),
392 released: false,
393 unreleased: false,
394 archived: false,
395 unarchived: false,
396 output: OutputFormat::Table,
397 }),
398 }),
399 };
400 assert!(matches!(cmd.command, JiraSubcommands::Version(_)));
401 }
402
403 #[tokio::test]
407 async fn jira_command_execute_version_arm() {
408 use crate::atlassian::auth::test_util::EnvGuard;
409
410 let server = wiremock::MockServer::start().await;
411 wiremock::Mock::given(wiremock::matchers::method("GET"))
412 .and(wiremock::matchers::path(
413 "/rest/api/3/project/PROJ/versions",
414 ))
415 .respond_with(
416 wiremock::ResponseTemplate::new(200).set_body_json(serde_json::json!([
417 {"id": "1", "name": "1.0", "released": false, "archived": false}
418 ])),
419 )
420 .mount(&server)
421 .await;
422
423 let guard = EnvGuard::take();
424 let _home = guard.set_credentials(&server.uri());
425
426 let cmd = JiraCommand {
427 command: JiraSubcommands::Version(version::VersionCommand {
428 command: version::VersionSubcommands::List(version::ListCommand {
429 project: "PROJ".to_string(),
430 released: false,
431 unreleased: false,
432 archived: false,
433 unarchived: false,
434 output: OutputFormat::Yaml,
435 }),
436 }),
437 };
438 assert!(cmd.execute().await.is_ok());
439 }
440
441 #[tokio::test]
444 async fn jira_command_execute_label_arm() {
445 use crate::atlassian::auth::test_util::EnvGuard;
446
447 let server = wiremock::MockServer::start().await;
448 wiremock::Mock::given(wiremock::matchers::method("PUT"))
449 .and(wiremock::matchers::path("/rest/api/3/issue/PROJ-1"))
450 .respond_with(wiremock::ResponseTemplate::new(204))
451 .mount(&server)
452 .await;
453
454 let guard = EnvGuard::take();
455 let _home = guard.set_credentials(&server.uri());
456
457 let cmd = JiraCommand {
458 command: JiraSubcommands::Label(label::LabelCommand {
459 command: label::LabelSubcommands::Add(label::AddCommand {
460 key: "PROJ-1".to_string(),
461 labels: vec!["backend".to_string()],
462 }),
463 }),
464 };
465 assert!(cmd.execute().await.is_ok());
466 }
467
468 #[tokio::test]
471 async fn jira_command_execute_component_arm() {
472 use crate::atlassian::auth::test_util::EnvGuard;
473
474 let server = wiremock::MockServer::start().await;
475 wiremock::Mock::given(wiremock::matchers::method("GET"))
476 .and(wiremock::matchers::path(
477 "/rest/api/3/project/PROJ/components",
478 ))
479 .respond_with(
480 wiremock::ResponseTemplate::new(200).set_body_json(serde_json::json!([
481 {"id": "10000", "name": "Backend"}
482 ])),
483 )
484 .mount(&server)
485 .await;
486
487 let guard = EnvGuard::take();
488 let _home = guard.set_credentials(&server.uri());
489
490 let cmd = JiraCommand {
491 command: JiraSubcommands::Component(component::ComponentCommand {
492 command: component::ComponentSubcommands::List(component::ListCommand {
493 project: "PROJ".to_string(),
494 output: OutputFormat::Yaml,
495 }),
496 }),
497 };
498 assert!(cmd.execute().await.is_ok());
499 }
500}