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