1mod args;
17mod batch;
18mod build;
19mod children;
20mod clone;
21mod decompose;
22mod edit;
23mod from_template;
24mod parent;
25mod refactor;
26mod relations;
27mod schedule;
28mod show;
29mod split;
30mod start;
31mod status;
32mod template;
33
34use anyhow::Result;
35
36use crate::config;
37
38pub use args::{
40 BatchEditArgs, BatchFieldArgs, BatchMode, BatchOperation, BatchStatusArgs, TaskArgs,
41 TaskBatchArgs, TaskBlocksArgs, TaskBuildArgs, TaskBuildRefactorArgs, TaskChildrenArgs,
42 TaskCloneArgs, TaskCommand, TaskDecomposeArgs, TaskDecomposeChildPolicyArg,
43 TaskDecomposeFormatArg, TaskDoneArgs, TaskEditArgs, TaskEditFieldArg, TaskFieldArgs,
44 TaskFromArgs, TaskFromCommand, TaskFromTemplateArgs, TaskMarkDuplicateArgs, TaskParentArgs,
45 TaskReadyArgs, TaskRejectArgs, TaskRelateArgs, TaskRelationFormat, TaskScheduleArgs,
46 TaskShowArgs, TaskSplitArgs, TaskStartArgs, TaskStatusArg, TaskStatusArgs, TaskTemplateArgs,
47 TaskTemplateBuildArgs, TaskTemplateCommand, TaskTemplateShowArgs, TaskUpdateArgs,
48};
49
50pub fn handle_task(args: TaskArgs, force: bool) -> Result<()> {
52 let resolved = config::resolve_from_cwd()?;
53
54 match args.command {
55 Some(TaskCommand::Ready(args)) => status::handle_ready(&args, force, &resolved),
56 Some(TaskCommand::Status(args)) => status::handle_status(&args, force, &resolved),
57 Some(TaskCommand::Done(args)) => status::handle_done(&args, force, &resolved),
58 Some(TaskCommand::Reject(args)) => status::handle_reject(&args, force, &resolved),
59 Some(TaskCommand::Field(args)) => edit::handle_field(&args, force, &resolved),
60 Some(TaskCommand::Edit(args)) => edit::handle_edit(&args, force, &resolved),
61 Some(TaskCommand::Update(args)) => edit::handle_update(&args, &resolved, force),
62 Some(TaskCommand::Build(args)) => build::handle(&args, force, &resolved),
63 Some(TaskCommand::Decompose(args)) => decompose::handle(&args, force, &resolved),
64 Some(TaskCommand::Template(template_args)) => template::handle(&resolved, &template_args),
65 Some(TaskCommand::BuildRefactor(args)) | Some(TaskCommand::Refactor(args)) => {
66 refactor::handle(&args, force, &resolved)
67 }
68 Some(TaskCommand::Show(args)) => show::handle(&args, &resolved),
69 Some(TaskCommand::Clone(args)) => clone::handle(&args, force, &resolved),
70 Some(TaskCommand::Batch(args)) => batch::handle(&args, force, &resolved),
71 Some(TaskCommand::Schedule(args)) => schedule::handle(&args, force, &resolved),
72 Some(TaskCommand::Relate(args)) => relations::handle_relate(&args, force, &resolved),
73 Some(TaskCommand::Blocks(args)) => relations::handle_blocks(&args, force, &resolved),
74 Some(TaskCommand::MarkDuplicate(args)) => {
75 relations::handle_mark_duplicate(&args, force, &resolved)
76 }
77 Some(TaskCommand::Split(args)) => split::handle(&args, force, &resolved),
78 Some(TaskCommand::Start(args)) => start::handle(&args, force, &resolved),
79 Some(TaskCommand::Children(args)) => children::handle(&args, &resolved),
80 Some(TaskCommand::Parent(args)) => parent::handle(&args, &resolved),
81 Some(TaskCommand::From(args)) => match args.command {
82 TaskFromCommand::Template(template_args) => {
83 from_template::handle(&resolved, &template_args, force)
84 }
85 },
86 None => {
87 build::handle(&args.build, force, &resolved)
89 }
90 }
91}
92
93#[cfg(test)]
94mod tests {
95 use clap::{CommandFactory, Parser};
96
97 use crate::cli::Cli;
98 use crate::cli::queue::QueueShowFormat;
99 use crate::cli::task::args::{BatchOperation, TaskEditFieldArg, TaskStatusArg};
100
101 #[test]
102 fn task_update_help_mentions_rp_examples() {
103 let mut cmd = Cli::command();
104 let task = cmd.find_subcommand_mut("task").expect("task subcommand");
105 let update = task
106 .find_subcommand_mut("update")
107 .expect("task update subcommand");
108 let help = update.render_long_help().to_string();
109
110 assert!(
111 help.contains("ralph task update --repo-prompt plan RQ-0001"),
112 "missing repo-prompt plan example: {help}"
113 );
114 assert!(
115 help.contains("ralph task update --repo-prompt off --fields scope,evidence RQ-0001"),
116 "missing repo-prompt off example: {help}"
117 );
118 assert!(
119 help.contains("ralph task update --approval-mode auto-edits --runner claude RQ-0001"),
120 "missing approval-mode example: {help}"
121 );
122 }
123
124 #[test]
125 fn task_show_help_mentions_examples() {
126 let mut cmd = Cli::command();
127 let task = cmd.find_subcommand_mut("task").expect("task subcommand");
128 let show = task
129 .find_subcommand_mut("show")
130 .expect("task show subcommand");
131 let help = show.render_long_help().to_string();
132
133 assert!(
134 help.contains("ralph task show RQ-0001"),
135 "missing show example: {help}"
136 );
137 assert!(
138 help.contains("--format compact"),
139 "missing format example: {help}"
140 );
141 }
142
143 #[test]
144 fn task_details_alias_parses() {
145 let cli =
146 Cli::try_parse_from(["ralph", "task", "details", "RQ-0001", "--format", "compact"])
147 .expect("parse");
148
149 match cli.command {
150 crate::cli::Command::Task(args) => match args.command {
151 Some(crate::cli::task::TaskCommand::Show(args)) => {
152 assert_eq!(args.task_id, "RQ-0001");
153 assert_eq!(args.format, QueueShowFormat::Compact);
154 }
155 _ => panic!("expected task show command"),
156 },
157 _ => panic!("expected task command"),
158 }
159 }
160
161 #[test]
162 fn task_build_parses_repo_prompt_and_effort_alias() {
163 let cli = Cli::try_parse_from([
164 "ralph",
165 "task",
166 "build",
167 "--repo-prompt",
168 "plan",
169 "-e",
170 "high",
171 "Add tests",
172 ])
173 .expect("parse");
174
175 match cli.command {
176 crate::cli::Command::Task(args) => match args.command {
177 Some(crate::cli::task::TaskCommand::Build(args)) => {
178 assert_eq!(args.repo_prompt, Some(crate::agent::RepoPromptMode::Plan));
179 assert_eq!(args.effort.as_deref(), Some("high"));
180 }
181 _ => panic!("expected task build command"),
182 },
183 _ => panic!("expected task command"),
184 }
185 }
186
187 #[test]
188 fn task_build_parses_runner_cli_overrides() {
189 let cli = Cli::try_parse_from([
190 "ralph",
191 "task",
192 "build",
193 "--approval-mode",
194 "yolo",
195 "--sandbox",
196 "disabled",
197 "Add tests",
198 ])
199 .expect("parse");
200
201 match cli.command {
202 crate::cli::Command::Task(args) => match args.command {
203 Some(crate::cli::task::TaskCommand::Build(args)) => {
204 assert_eq!(args.runner_cli.approval_mode.as_deref(), Some("yolo"));
205 assert_eq!(args.runner_cli.sandbox.as_deref(), Some("disabled"));
206 }
207 _ => panic!("expected task build command"),
208 },
209 _ => panic!("expected task command"),
210 }
211 }
212
213 #[test]
214 fn task_decompose_parses_preview_and_limits() {
215 let cli = Cli::try_parse_from([
216 "ralph",
217 "task",
218 "decompose",
219 "--preview",
220 "--attach-to",
221 "RQ-0042",
222 "--child-policy",
223 "append",
224 "--with-dependencies",
225 "--format",
226 "json",
227 "--max-depth",
228 "4",
229 "--max-children",
230 "6",
231 "--max-nodes",
232 "24",
233 "RQ-0001",
234 ])
235 .expect("parse");
236
237 match cli.command {
238 crate::cli::Command::Task(args) => match args.command {
239 Some(crate::cli::task::TaskCommand::Decompose(args)) => {
240 assert!(args.preview);
241 assert!(!args.write);
242 assert_eq!(args.attach_to.as_deref(), Some("RQ-0042"));
243 assert_eq!(
244 args.child_policy,
245 crate::cli::task::TaskDecomposeChildPolicyArg::Append
246 );
247 assert!(args.with_dependencies);
248 assert_eq!(args.format, crate::cli::task::TaskDecomposeFormatArg::Json);
249 assert_eq!(args.max_depth, 4);
250 assert_eq!(args.max_children, 6);
251 assert_eq!(args.max_nodes, 24);
252 assert_eq!(args.source, vec!["RQ-0001"]);
253 }
254 _ => panic!("expected task decompose command"),
255 },
256 _ => panic!("expected task command"),
257 }
258 }
259
260 #[test]
261 fn task_decompose_parses_runner_overrides() {
262 let cli = Cli::try_parse_from([
263 "ralph",
264 "task",
265 "decompose",
266 "--runner",
267 "codex",
268 "--model",
269 "gpt-5.4",
270 "-e",
271 "high",
272 "--repo-prompt",
273 "tools",
274 "--approval-mode",
275 "auto-edits",
276 "Plan queue migration",
277 ])
278 .expect("parse");
279
280 match cli.command {
281 crate::cli::Command::Task(args) => match args.command {
282 Some(crate::cli::task::TaskCommand::Decompose(args)) => {
283 assert_eq!(args.runner.as_deref(), Some("codex"));
284 assert_eq!(args.model.as_deref(), Some("gpt-5.4"));
285 assert_eq!(args.effort.as_deref(), Some("high"));
286 assert_eq!(args.repo_prompt, Some(crate::agent::RepoPromptMode::Tools));
287 assert_eq!(args.runner_cli.approval_mode.as_deref(), Some("auto-edits"));
288 }
289 _ => panic!("expected task decompose command"),
290 },
291 _ => panic!("expected task command"),
292 }
293 }
294
295 #[test]
296 fn task_decompose_help_mentions_write_example() {
297 let mut cmd = Cli::command();
298 let task = cmd.find_subcommand_mut("task").expect("task subcommand");
299 let decompose = task
300 .find_subcommand_mut("decompose")
301 .expect("task decompose subcommand");
302 let help = decompose.render_long_help().to_string();
303
304 assert!(
305 help.contains("Improve webhook reliability\" --write"),
306 "missing write example: {help}"
307 );
308 assert!(
309 help.contains("--attach-to RQ-0042"),
310 "missing attach example: {help}"
311 );
312 assert!(
313 help.contains("--format json"),
314 "missing json output example: {help}"
315 );
316 }
317
318 #[test]
319 fn task_update_parses_repo_prompt_and_effort_alias() {
320 let cli = Cli::try_parse_from([
321 "ralph",
322 "task",
323 "update",
324 "--repo-prompt",
325 "off",
326 "-e",
327 "low",
328 "RQ-0001",
329 ])
330 .expect("parse");
331
332 match cli.command {
333 crate::cli::Command::Task(args) => match args.command {
334 Some(crate::cli::task::TaskCommand::Update(args)) => {
335 assert_eq!(args.repo_prompt, Some(crate::agent::RepoPromptMode::Off));
336 assert_eq!(args.effort.as_deref(), Some("low"));
337 assert_eq!(args.task_id.as_deref(), Some("RQ-0001"));
338 }
339 _ => panic!("expected task update command"),
340 },
341 _ => panic!("expected task command"),
342 }
343 }
344
345 #[test]
346 fn task_update_parses_runner_cli_overrides() {
347 let cli = Cli::try_parse_from([
348 "ralph",
349 "task",
350 "update",
351 "--approval-mode",
352 "auto-edits",
353 "--sandbox",
354 "disabled",
355 "RQ-0001",
356 ])
357 .expect("parse");
358
359 match cli.command {
360 crate::cli::Command::Task(args) => match args.command {
361 Some(crate::cli::task::TaskCommand::Update(args)) => {
362 assert_eq!(args.runner_cli.approval_mode.as_deref(), Some("auto-edits"));
363 assert_eq!(args.runner_cli.sandbox.as_deref(), Some("disabled"));
364 assert_eq!(args.task_id.as_deref(), Some("RQ-0001"));
365 }
366 _ => panic!("expected task update command"),
367 },
368 _ => panic!("expected task command"),
369 }
370 }
371
372 #[test]
373 fn task_edit_parses_dry_run_flag() {
374 let cli = Cli::try_parse_from([
375 "ralph",
376 "task",
377 "edit",
378 "--dry-run",
379 "title",
380 "New title",
381 "RQ-0001",
382 ])
383 .expect("parse");
384
385 match cli.command {
386 crate::cli::Command::Task(args) => match args.command {
387 Some(crate::cli::task::TaskCommand::Edit(args)) => {
388 assert!(args.dry_run);
389 assert_eq!(args.task_ids, vec!["RQ-0001"]);
390 assert_eq!(args.value, "New title");
391 }
392 _ => panic!("expected task edit command"),
393 },
394 _ => panic!("expected task command"),
395 }
396 }
397
398 #[test]
399 fn task_edit_without_dry_run_defaults_to_false() {
400 let cli = Cli::try_parse_from(["ralph", "task", "edit", "title", "New title", "RQ-0001"])
401 .expect("parse");
402
403 match cli.command {
404 crate::cli::Command::Task(args) => match args.command {
405 Some(crate::cli::task::TaskCommand::Edit(args)) => {
406 assert!(!args.dry_run);
407 }
408 _ => panic!("expected task edit command"),
409 },
410 _ => panic!("expected task command"),
411 }
412 }
413
414 #[test]
415 fn task_update_parses_dry_run_flag() {
416 let cli = Cli::try_parse_from(["ralph", "task", "update", "--dry-run", "RQ-0001"])
417 .expect("parse");
418
419 match cli.command {
420 crate::cli::Command::Task(args) => match args.command {
421 Some(crate::cli::task::TaskCommand::Update(args)) => {
422 assert!(args.dry_run);
423 assert_eq!(args.task_id.as_deref(), Some("RQ-0001"));
424 }
425 _ => panic!("expected task update command"),
426 },
427 _ => panic!("expected task command"),
428 }
429 }
430
431 #[test]
432 fn task_update_without_dry_run_defaults_to_false() {
433 let cli = Cli::try_parse_from(["ralph", "task", "update", "RQ-0001"]).expect("parse");
434
435 match cli.command {
436 crate::cli::Command::Task(args) => match args.command {
437 Some(crate::cli::task::TaskCommand::Update(args)) => {
438 assert!(!args.dry_run);
439 }
440 _ => panic!("expected task update command"),
441 },
442 _ => panic!("expected task command"),
443 }
444 }
445
446 #[test]
447 fn task_refactor_parses() {
448 let cli = Cli::try_parse_from(["ralph", "task", "refactor"]).expect("parse");
449 match cli.command {
450 crate::cli::Command::Task(args) => match args.command {
451 Some(crate::cli::task::TaskCommand::Refactor(_)) => {}
452 _ => panic!("expected task refactor command"),
453 },
454 _ => panic!("expected task command"),
455 }
456 }
457
458 #[test]
459 fn task_ref_alias_parses() {
460 let cli =
461 Cli::try_parse_from(["ralph", "task", "ref", "--threshold", "800"]).expect("parse");
462 match cli.command {
463 crate::cli::Command::Task(args) => match args.command {
464 Some(crate::cli::task::TaskCommand::Refactor(args)) => {
465 assert_eq!(args.threshold, 800);
466 }
467 _ => panic!("expected task refactor command via alias"),
468 },
469 _ => panic!("expected task command"),
470 }
471 }
472
473 #[test]
474 fn task_build_refactor_parses() {
475 let cli = Cli::try_parse_from(["ralph", "task", "build-refactor", "--threshold", "700"])
476 .expect("parse");
477 match cli.command {
478 crate::cli::Command::Task(args) => match args.command {
479 Some(crate::cli::task::TaskCommand::BuildRefactor(args)) => {
480 assert_eq!(args.threshold, 700);
481 }
482 _ => panic!("expected task build-refactor command"),
483 },
484 _ => panic!("expected task command"),
485 }
486 }
487
488 #[test]
489 fn task_clone_parses() {
490 let cli = Cli::try_parse_from(["ralph", "task", "clone", "RQ-0001"]).expect("parse");
491 match cli.command {
492 crate::cli::Command::Task(args) => match args.command {
493 Some(crate::cli::task::TaskCommand::Clone(args)) => {
494 assert_eq!(args.task_id, "RQ-0001");
495 assert!(!args.dry_run);
496 }
497 _ => panic!("expected task clone command"),
498 },
499 _ => panic!("expected task command"),
500 }
501 }
502
503 #[test]
504 fn task_duplicate_alias_parses() {
505 let cli = Cli::try_parse_from(["ralph", "task", "duplicate", "RQ-0001"]).expect("parse");
506 match cli.command {
507 crate::cli::Command::Task(args) => match args.command {
508 Some(crate::cli::task::TaskCommand::Clone(args)) => {
509 assert_eq!(args.task_id, "RQ-0001");
510 }
511 _ => panic!("expected task clone command via duplicate alias"),
512 },
513 _ => panic!("expected task command"),
514 }
515 }
516
517 #[test]
518 fn task_clone_parses_status_flag() {
519 let cli = Cli::try_parse_from(["ralph", "task", "clone", "--status", "todo", "RQ-0001"])
520 .expect("parse");
521 match cli.command {
522 crate::cli::Command::Task(args) => match args.command {
523 Some(crate::cli::task::TaskCommand::Clone(args)) => {
524 assert_eq!(args.task_id, "RQ-0001");
525 assert_eq!(args.status, Some(TaskStatusArg::Todo));
526 }
527 _ => panic!("expected task clone command"),
528 },
529 _ => panic!("expected task command"),
530 }
531 }
532
533 #[test]
534 fn task_clone_parses_title_prefix() {
535 let cli = Cli::try_parse_from([
536 "ralph",
537 "task",
538 "clone",
539 "--title-prefix",
540 "[Follow-up] ",
541 "RQ-0001",
542 ])
543 .expect("parse");
544 match cli.command {
545 crate::cli::Command::Task(args) => match args.command {
546 Some(crate::cli::task::TaskCommand::Clone(args)) => {
547 assert_eq!(args.task_id, "RQ-0001");
548 assert_eq!(args.title_prefix, Some("[Follow-up] ".to_string()));
549 }
550 _ => panic!("expected task clone command"),
551 },
552 _ => panic!("expected task command"),
553 }
554 }
555
556 #[test]
557 fn task_clone_parses_dry_run_flag() {
558 let cli =
559 Cli::try_parse_from(["ralph", "task", "clone", "--dry-run", "RQ-0001"]).expect("parse");
560 match cli.command {
561 crate::cli::Command::Task(args) => match args.command {
562 Some(crate::cli::task::TaskCommand::Clone(args)) => {
563 assert_eq!(args.task_id, "RQ-0001");
564 assert!(args.dry_run);
565 }
566 _ => panic!("expected task clone command"),
567 },
568 _ => panic!("expected task command"),
569 }
570 }
571
572 #[test]
573 fn task_clone_help_mentions_examples() {
574 let mut cmd = Cli::command();
575 let task = cmd.find_subcommand_mut("task").expect("task subcommand");
576 let clone = task
577 .find_subcommand_mut("clone")
578 .expect("task clone subcommand");
579 let help = clone.render_long_help().to_string();
580
581 assert!(
582 help.contains("ralph task clone RQ-0001"),
583 "missing clone example: {help}"
584 );
585 assert!(
586 help.contains("--status"),
587 "missing --status example: {help}"
588 );
589 assert!(
590 help.contains("--title-prefix"),
591 "missing --title-prefix example: {help}"
592 );
593 assert!(
594 help.contains("ralph task duplicate"),
595 "missing duplicate alias example: {help}"
596 );
597 }
598
599 #[test]
600 fn task_batch_status_parses_multiple_ids() {
601 let cli = Cli::try_parse_from([
602 "ralph", "task", "batch", "status", "doing", "RQ-0001", "RQ-0002", "RQ-0003",
603 ])
604 .expect("parse");
605 match cli.command {
606 crate::cli::Command::Task(args) => match args.command {
607 Some(crate::cli::task::TaskCommand::Batch(args)) => match args.operation {
608 BatchOperation::Status(status_args) => {
609 assert_eq!(status_args.status, TaskStatusArg::Doing);
610 assert_eq!(
611 status_args.select.task_ids,
612 vec!["RQ-0001", "RQ-0002", "RQ-0003"]
613 );
614 assert!(!args.dry_run);
615 assert!(!args.continue_on_error);
616 }
617 _ => panic!("expected batch status operation"),
618 },
619 _ => panic!("expected task batch command"),
620 },
621 _ => panic!("expected task command"),
622 }
623 }
624
625 #[test]
626 fn task_batch_status_parses_tag_filter() {
627 let cli = Cli::try_parse_from([
628 "ralph",
629 "task",
630 "batch",
631 "status",
632 "doing",
633 "--tag-filter",
634 "rust",
635 "--tag-filter",
636 "cli",
637 ])
638 .expect("parse");
639 match cli.command {
640 crate::cli::Command::Task(args) => match args.command {
641 Some(crate::cli::task::TaskCommand::Batch(args)) => match args.operation {
642 BatchOperation::Status(status_args) => {
643 assert_eq!(status_args.status, TaskStatusArg::Doing);
644 assert!(status_args.select.task_ids.is_empty());
645 assert_eq!(status_args.select.tag_filter, vec!["rust", "cli"]);
646 }
647 _ => panic!("expected batch status operation"),
648 },
649 _ => panic!("expected task batch command"),
650 },
651 _ => panic!("expected task command"),
652 }
653 }
654
655 #[test]
656 fn task_batch_field_parses_multiple_ids() {
657 let cli = Cli::try_parse_from([
658 "ralph", "task", "batch", "field", "severity", "high", "RQ-0001", "RQ-0002",
659 ])
660 .expect("parse");
661 match cli.command {
662 crate::cli::Command::Task(args) => match args.command {
663 Some(crate::cli::task::TaskCommand::Batch(args)) => match args.operation {
664 BatchOperation::Field(field_args) => {
665 assert_eq!(field_args.key, "severity");
666 assert_eq!(field_args.value, "high");
667 assert_eq!(field_args.select.task_ids, vec!["RQ-0001", "RQ-0002"]);
668 }
669 _ => panic!("expected batch field operation"),
670 },
671 _ => panic!("expected task batch command"),
672 },
673 _ => panic!("expected task command"),
674 }
675 }
676
677 #[test]
678 fn task_batch_edit_parses_dry_run() {
679 let cli = Cli::try_parse_from([
680 "ralph",
681 "task",
682 "batch",
683 "--dry-run",
684 "edit",
685 "priority",
686 "high",
687 "RQ-0001",
688 "RQ-0002",
689 ])
690 .expect("parse");
691 match cli.command {
692 crate::cli::Command::Task(args) => match args.command {
693 Some(crate::cli::task::TaskCommand::Batch(args)) => {
694 assert!(args.dry_run);
695 assert!(!args.continue_on_error);
696 match args.operation {
697 BatchOperation::Edit(edit_args) => {
698 assert_eq!(edit_args.field, TaskEditFieldArg::Priority);
699 assert_eq!(edit_args.value, "high");
700 assert_eq!(edit_args.select.task_ids, vec!["RQ-0001", "RQ-0002"]);
701 }
702 _ => panic!("expected batch edit operation"),
703 }
704 }
705 _ => panic!("expected task batch command"),
706 },
707 _ => panic!("expected task command"),
708 }
709 }
710
711 #[test]
712 fn task_batch_parses_continue_on_error() {
713 let cli = Cli::try_parse_from([
714 "ralph",
715 "task",
716 "batch",
717 "--continue-on-error",
718 "status",
719 "doing",
720 "RQ-0001",
721 "RQ-0002",
722 ])
723 .expect("parse");
724 match cli.command {
725 crate::cli::Command::Task(args) => match args.command {
726 Some(crate::cli::task::TaskCommand::Batch(args)) => {
727 assert!(!args.dry_run);
728 assert!(args.continue_on_error);
729 match args.operation {
730 BatchOperation::Status(status_args) => {
731 assert_eq!(status_args.status, TaskStatusArg::Doing);
732 }
733 _ => panic!("expected batch status operation"),
734 }
735 }
736 _ => panic!("expected task batch command"),
737 },
738 _ => panic!("expected task command"),
739 }
740 }
741
742 #[test]
743 fn task_batch_help_mentions_examples() {
744 let mut cmd = Cli::command();
745 let task = cmd.find_subcommand_mut("task").expect("task subcommand");
746 let batch = task
747 .find_subcommand_mut("batch")
748 .expect("task batch subcommand");
749 let help = batch.render_long_help().to_string();
750
751 assert!(
752 help.contains("ralph task batch status doing"),
753 "missing batch status example: {help}"
754 );
755 assert!(
756 help.contains("--tag-filter"),
757 "missing --tag-filter example: {help}"
758 );
759 assert!(
760 help.contains("--dry-run"),
761 "missing --dry-run example: {help}"
762 );
763 assert!(
764 help.contains("--continue-on-error"),
765 "missing --continue-on-error example: {help}"
766 );
767 }
768
769 #[test]
770 fn task_status_parses_multiple_ids() {
771 let cli = Cli::try_parse_from([
772 "ralph", "task", "status", "doing", "RQ-0001", "RQ-0002", "RQ-0003",
773 ])
774 .expect("parse");
775 match cli.command {
776 crate::cli::Command::Task(args) => match args.command {
777 Some(crate::cli::task::TaskCommand::Status(args)) => {
778 assert_eq!(args.status, TaskStatusArg::Doing);
779 assert_eq!(args.task_ids, vec!["RQ-0001", "RQ-0002", "RQ-0003"]);
780 }
781 _ => panic!("expected task status command"),
782 },
783 _ => panic!("expected task command"),
784 }
785 }
786
787 #[test]
788 fn task_status_parses_tag_filter() {
789 let cli = Cli::try_parse_from([
790 "ralph",
791 "task",
792 "status",
793 "doing",
794 "--tag-filter",
795 "rust",
796 "--tag-filter",
797 "cli",
798 ])
799 .expect("parse");
800 match cli.command {
801 crate::cli::Command::Task(args) => match args.command {
802 Some(crate::cli::task::TaskCommand::Status(args)) => {
803 assert_eq!(args.status, TaskStatusArg::Doing);
804 assert!(args.task_ids.is_empty());
805 assert_eq!(args.tag_filter, vec!["rust", "cli"]);
806 }
807 _ => panic!("expected task status command"),
808 },
809 _ => panic!("expected task command"),
810 }
811 }
812
813 #[test]
814 fn task_field_parses_multiple_ids() {
815 let cli = Cli::try_parse_from([
816 "ralph", "task", "field", "severity", "high", "RQ-0001", "RQ-0002",
817 ])
818 .expect("parse");
819 match cli.command {
820 crate::cli::Command::Task(args) => match args.command {
821 Some(crate::cli::task::TaskCommand::Field(args)) => {
822 assert_eq!(args.key, "severity");
823 assert_eq!(args.value, "high");
824 assert_eq!(args.task_ids, vec!["RQ-0001", "RQ-0002"]);
825 }
826 _ => panic!("expected task field command"),
827 },
828 _ => panic!("expected task command"),
829 }
830 }
831
832 #[test]
833 fn task_field_parses_dry_run_flag() {
834 let cli = Cli::try_parse_from([
835 "ralph",
836 "task",
837 "field",
838 "--dry-run",
839 "severity",
840 "high",
841 "RQ-0001",
842 ])
843 .expect("parse");
844 match cli.command {
845 crate::cli::Command::Task(args) => match args.command {
846 Some(crate::cli::task::TaskCommand::Field(args)) => {
847 assert!(args.dry_run);
848 assert_eq!(args.key, "severity");
849 assert_eq!(args.value, "high");
850 assert_eq!(args.task_ids, vec!["RQ-0001"]);
851 }
852 _ => panic!("expected task field command"),
853 },
854 _ => panic!("expected task command"),
855 }
856 }
857
858 #[test]
859 fn task_field_without_dry_run_defaults_to_false() {
860 let cli = Cli::try_parse_from(["ralph", "task", "field", "severity", "high", "RQ-0001"])
861 .expect("parse");
862 match cli.command {
863 crate::cli::Command::Task(args) => match args.command {
864 Some(crate::cli::task::TaskCommand::Field(args)) => {
865 assert!(!args.dry_run);
866 }
867 _ => panic!("expected task field command"),
868 },
869 _ => panic!("expected task command"),
870 }
871 }
872
873 #[test]
874 fn task_edit_parses_multiple_ids() {
875 let cli = Cli::try_parse_from([
876 "ralph", "task", "edit", "priority", "high", "RQ-0001", "RQ-0002",
877 ])
878 .expect("parse");
879 match cli.command {
880 crate::cli::Command::Task(args) => match args.command {
881 Some(crate::cli::task::TaskCommand::Edit(args)) => {
882 assert_eq!(args.field, TaskEditFieldArg::Priority);
883 assert_eq!(args.value, "high");
884 assert_eq!(args.task_ids, vec!["RQ-0001", "RQ-0002"]);
885 }
886 _ => panic!("expected task edit command"),
887 },
888 _ => panic!("expected task command"),
889 }
890 }
891}