1use crate::approval::types::{PermissionReply, QuestionRequest};
6
7#[derive(Debug, Clone, PartialEq)]
9pub enum MatchResult {
10 PermissionReply {
12 reply: PermissionReply,
13 message: Option<String>,
14 },
15 QuestionAnswer { answers: Vec<Vec<String>> },
17 QuestionReject,
19 NoMatch,
21}
22
23pub fn normalize(text: &str) -> String {
25 let lower = text.to_lowercase();
26 let trimmed = lower.trim();
27 let stripped =
29 trimmed.trim_end_matches(|c: char| matches!(c, '.' | '!' | '?' | ',' | ';' | ':'));
30 stripped.split_whitespace().collect::<Vec<_>>().join(" ")
32}
33
34const ONCE_PATTERNS: &[&str] = &[
36 "allow",
37 "allow once",
38 "allow it",
39 "allow this",
40 "yes",
41 "yeah",
42 "yep",
43 "ok",
44 "okay",
45 "sure",
46 "proceed",
47 "go ahead",
48 "do it",
49 "approve",
50 "approve once",
51 "approve it",
52 "approve this",
53 "accept",
54 "run it",
55 "go for it",
56 "permit",
57 "execute",
58];
59
60const ALWAYS_PATTERNS: &[&str] = &["always", "always allow", "trust", "trust it", "allow all"];
62
63const REJECT_PATTERNS: &[&str] = &[
65 "reject",
66 "deny",
67 "no",
68 "nope",
69 "cancel",
70 "stop",
71 "don't",
72 "do not",
73 "refuse",
74 "block",
75 "skip",
76 "decline",
77 "dismiss",
78 "not allowed",
79 "nah",
80 "don't do it",
81];
82
83const REJECT_PREFIXES: &[&str] = &["no", "nope", "reject", "deny", "cancel", "don't", "refuse"];
85
86const QUESTION_REJECT_PATTERNS: &[&str] = &[
88 "skip",
89 "dismiss",
90 "cancel",
91 "reject",
92 "none",
93 "never mind",
94 "nevermind",
95];
96
97fn parse_number_word(word: &str) -> Option<usize> {
99 match word {
100 "one" | "first" | "1" => Some(1),
101 "two" | "second" | "2" => Some(2),
102 "three" | "third" | "3" => Some(3),
103 "four" | "fourth" | "4" => Some(4),
104 "five" | "fifth" | "5" => Some(5),
105 "six" | "sixth" | "6" => Some(6),
106 "seven" | "seventh" | "7" => Some(7),
107 "eight" | "eighth" | "8" => Some(8),
108 "nine" | "ninth" | "9" => Some(9),
109 "ten" | "tenth" | "10" => Some(10),
110 _ => None,
111 }
112}
113
114pub fn match_permission_command(text: &str) -> MatchResult {
118 let normalized = normalize(text);
119
120 for pattern in ALWAYS_PATTERNS {
122 if normalized == *pattern {
123 return MatchResult::PermissionReply {
124 reply: PermissionReply::Always,
125 message: None,
126 };
127 }
128 }
129
130 for pattern in ONCE_PATTERNS {
132 if normalized == *pattern {
133 return MatchResult::PermissionReply {
134 reply: PermissionReply::Once,
135 message: None,
136 };
137 }
138 }
139
140 for pattern in REJECT_PATTERNS {
142 if normalized == *pattern {
143 return MatchResult::PermissionReply {
144 reply: PermissionReply::Reject,
145 message: None,
146 };
147 }
148 }
149
150 for prefix in REJECT_PREFIXES {
152 if let Some(after) = normalized.strip_prefix(prefix) {
153 let after = after.trim_start_matches(|c: char| c == ',' || c.is_whitespace());
154 if !after.is_empty() {
155 return MatchResult::PermissionReply {
156 reply: PermissionReply::Reject,
157 message: Some(after.to_string()),
158 };
159 }
160 }
161 }
162
163 MatchResult::NoMatch
164}
165
166pub fn match_question_answer(text: &str, question: &QuestionRequest) -> MatchResult {
170 let normalized = normalize(text);
171
172 for pattern in QUESTION_REJECT_PATTERNS {
174 if normalized == *pattern {
175 return MatchResult::QuestionReject;
176 }
177 }
178
179 let mut all_answers: Vec<Vec<String>> = Vec::new();
181
182 for q in &question.questions {
183 let options = &q.options;
184
185 let exact = options.iter().find(|o| normalize(&o.label) == normalized);
187 if let Some(opt) = exact {
188 all_answers.push(vec![opt.label.clone()]);
189 continue;
190 }
191
192 let contains = options
194 .iter()
195 .find(|o| normalized.contains(&normalize(&o.label)));
196 if let Some(opt) = contains {
197 all_answers.push(vec![opt.label.clone()]);
198 continue;
199 }
200
201 let numeric = if let Some(after_option) = normalized.strip_prefix("option ") {
204 parse_number_word(after_option.trim())
205 } else {
206 parse_number_word(&normalized)
208 };
209
210 if let Some(idx) = numeric {
211 if idx >= 1 && idx <= options.len() {
212 all_answers.push(vec![options[idx - 1].label.clone()]);
213 continue;
214 }
215 }
216
217 if q.custom {
219 all_answers.push(vec![text.trim().to_string()]);
220 continue;
221 }
222
223 return MatchResult::NoMatch;
225 }
226
227 if all_answers.is_empty() {
228 MatchResult::NoMatch
229 } else {
230 MatchResult::QuestionAnswer {
231 answers: all_answers,
232 }
233 }
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239 use crate::approval::types::{QuestionInfo, QuestionOption, QuestionRequest};
240
241 fn make_question(options: &[&str], custom: bool) -> QuestionRequest {
242 QuestionRequest {
243 id: "test-id".to_string(),
244 questions: vec![QuestionInfo {
245 question: "What do you want to do?".to_string(),
246 options: options
247 .iter()
248 .map(|&l| QuestionOption {
249 label: l.to_string(),
250 })
251 .collect(),
252 custom,
253 }],
254 }
255 }
256
257 #[test]
259 fn test_once_allow() {
260 assert!(matches!(
261 match_permission_command("allow"),
262 MatchResult::PermissionReply {
263 reply: PermissionReply::Once,
264 ..
265 }
266 ));
267 }
268 #[test]
269 fn test_once_yes() {
270 assert!(matches!(
271 match_permission_command("yes"),
272 MatchResult::PermissionReply {
273 reply: PermissionReply::Once,
274 ..
275 }
276 ));
277 }
278 #[test]
279 fn test_once_ok() {
280 assert!(matches!(
281 match_permission_command("ok"),
282 MatchResult::PermissionReply {
283 reply: PermissionReply::Once,
284 ..
285 }
286 ));
287 }
288 #[test]
289 fn test_once_okay() {
290 assert!(matches!(
291 match_permission_command("okay"),
292 MatchResult::PermissionReply {
293 reply: PermissionReply::Once,
294 ..
295 }
296 ));
297 }
298 #[test]
299 fn test_once_sure() {
300 assert!(matches!(
301 match_permission_command("sure"),
302 MatchResult::PermissionReply {
303 reply: PermissionReply::Once,
304 ..
305 }
306 ));
307 }
308 #[test]
309 fn test_once_proceed() {
310 assert!(matches!(
311 match_permission_command("proceed"),
312 MatchResult::PermissionReply {
313 reply: PermissionReply::Once,
314 ..
315 }
316 ));
317 }
318 #[test]
319 fn test_once_approve() {
320 assert!(matches!(
321 match_permission_command("approve"),
322 MatchResult::PermissionReply {
323 reply: PermissionReply::Once,
324 ..
325 }
326 ));
327 }
328 #[test]
329 fn test_once_execute() {
330 assert!(matches!(
331 match_permission_command("execute"),
332 MatchResult::PermissionReply {
333 reply: PermissionReply::Once,
334 ..
335 }
336 ));
337 }
338 #[test]
339 fn test_once_accept() {
340 assert!(matches!(
341 match_permission_command("accept"),
342 MatchResult::PermissionReply {
343 reply: PermissionReply::Once,
344 ..
345 }
346 ));
347 }
348 #[test]
349 fn test_once_yeah() {
350 assert!(matches!(
351 match_permission_command("yeah"),
352 MatchResult::PermissionReply {
353 reply: PermissionReply::Once,
354 ..
355 }
356 ));
357 }
358 #[test]
359 fn test_once_with_punctuation() {
360 assert!(matches!(
362 match_permission_command("yes."),
363 MatchResult::PermissionReply {
364 reply: PermissionReply::Once,
365 ..
366 }
367 ));
368 }
369
370 #[test]
372 fn test_always_always() {
373 assert!(matches!(
374 match_permission_command("always"),
375 MatchResult::PermissionReply {
376 reply: PermissionReply::Always,
377 ..
378 }
379 ));
380 }
381 #[test]
382 fn test_always_always_allow() {
383 assert!(matches!(
384 match_permission_command("always allow"),
385 MatchResult::PermissionReply {
386 reply: PermissionReply::Always,
387 ..
388 }
389 ));
390 }
391 #[test]
392 fn test_always_trust() {
393 assert!(matches!(
394 match_permission_command("trust"),
395 MatchResult::PermissionReply {
396 reply: PermissionReply::Always,
397 ..
398 }
399 ));
400 }
401 #[test]
402 fn test_always_trust_it() {
403 assert!(matches!(
404 match_permission_command("trust it"),
405 MatchResult::PermissionReply {
406 reply: PermissionReply::Always,
407 ..
408 }
409 ));
410 }
411 #[test]
412 fn test_always_allow_all() {
413 assert!(matches!(
414 match_permission_command("allow all"),
415 MatchResult::PermissionReply {
416 reply: PermissionReply::Always,
417 ..
418 }
419 ));
420 }
421
422 #[test]
424 fn test_reject_no() {
425 assert!(matches!(
426 match_permission_command("no"),
427 MatchResult::PermissionReply {
428 reply: PermissionReply::Reject,
429 ..
430 }
431 ));
432 }
433 #[test]
434 fn test_reject_reject() {
435 assert!(matches!(
436 match_permission_command("reject"),
437 MatchResult::PermissionReply {
438 reply: PermissionReply::Reject,
439 ..
440 }
441 ));
442 }
443 #[test]
444 fn test_reject_deny() {
445 assert!(matches!(
446 match_permission_command("deny"),
447 MatchResult::PermissionReply {
448 reply: PermissionReply::Reject,
449 ..
450 }
451 ));
452 }
453 #[test]
454 fn test_reject_cancel() {
455 assert!(matches!(
456 match_permission_command("cancel"),
457 MatchResult::PermissionReply {
458 reply: PermissionReply::Reject,
459 ..
460 }
461 ));
462 }
463 #[test]
464 fn test_reject_nope() {
465 assert!(matches!(
466 match_permission_command("nope"),
467 MatchResult::PermissionReply {
468 reply: PermissionReply::Reject,
469 ..
470 }
471 ));
472 }
473
474 #[test]
476 fn test_reject_with_message() {
477 let result = match_permission_command("no, try something else");
478 match result {
479 MatchResult::PermissionReply {
480 reply: PermissionReply::Reject,
481 message,
482 } => {
483 assert_eq!(message, Some("try something else".to_string()));
484 }
485 _ => panic!("Expected reject with message"),
486 }
487 }
488
489 #[test]
491 fn test_no_match() {
492 assert_eq!(
493 match_permission_command("hello world"),
494 MatchResult::NoMatch
495 );
496 }
497 #[test]
498 fn test_no_match_empty() {
499 assert_eq!(match_permission_command(""), MatchResult::NoMatch);
500 }
501
502 #[test]
504 fn test_question_exact_label() {
505 let q = make_question(&["Continue", "Cancel", "Retry"], true);
506 let result = match_question_answer("continue", &q);
507 assert!(matches!(result, MatchResult::QuestionAnswer { .. }));
508 }
509
510 #[test]
511 fn test_question_numeric_option_1() {
512 let q = make_question(&["Yes", "No"], true);
513 let result = match_question_answer("option 1", &q);
514 assert!(matches!(result, MatchResult::QuestionAnswer { .. }));
515 }
516
517 #[test]
518 fn test_question_numeric_word_first() {
519 let q = make_question(&["Alpha", "Beta"], true);
520 let result = match_question_answer("first", &q);
521 assert!(
522 matches!(result, MatchResult::QuestionAnswer { answers } if answers[0] == vec!["Alpha"])
523 );
524 }
525
526 #[test]
527 fn test_question_numeric_word_one() {
528 let q = make_question(&["Alpha", "Beta"], true);
529 let result = match_question_answer("one", &q);
530 assert!(
531 matches!(result, MatchResult::QuestionAnswer { answers } if answers[0] == vec!["Alpha"])
532 );
533 }
534
535 #[test]
536 fn test_question_reject_skip() {
537 let q = make_question(&["Yes", "No"], true);
538 assert_eq!(
539 match_question_answer("skip", &q),
540 MatchResult::QuestionReject
541 );
542 }
543
544 #[test]
545 fn test_question_reject_dismiss() {
546 let q = make_question(&["Yes", "No"], true);
547 assert_eq!(
548 match_question_answer("dismiss", &q),
549 MatchResult::QuestionReject
550 );
551 }
552
553 #[test]
554 fn test_question_custom_answer() {
555 let q = make_question(&["Yes", "No"], true);
556 let result = match_question_answer("do something custom", &q);
557 assert!(matches!(result, MatchResult::QuestionAnswer { .. }));
558 }
559
560 #[test]
561 fn test_question_no_match_no_custom() {
562 let q = make_question(&["Yes", "No"], false);
563 let result = match_question_answer("do something custom", &q);
564 assert_eq!(result, MatchResult::NoMatch);
565 }
566
567 #[test]
568 fn test_normalize_punctuation() {
569 assert_eq!(normalize("yes."), "yes");
570 assert_eq!(normalize("Allow!"), "allow");
571 assert_eq!(normalize(" ok "), "ok");
572 }
573
574 #[test]
576 fn test_once_all_patterns() {
577 let expected_once = [
578 "allow",
579 "allow once",
580 "allow it",
581 "allow this",
582 "yes",
583 "yeah",
584 "yep",
585 "ok",
586 "okay",
587 "sure",
588 "proceed",
589 "go ahead",
590 "do it",
591 "approve",
592 "approve once",
593 "approve it",
594 "approve this",
595 "accept",
596 "run it",
597 "go for it",
598 "permit",
599 "execute",
600 ];
601 assert_eq!(
602 expected_once.len(),
603 22,
604 "Must have exactly 22 once patterns"
605 );
606 for pattern in &expected_once {
607 assert!(
608 matches!(
609 match_permission_command(pattern),
610 MatchResult::PermissionReply {
611 reply: PermissionReply::Once,
612 ..
613 }
614 ),
615 "Pattern '{}' should match Once",
616 pattern
617 );
618 }
619 }
620
621 #[test]
622 fn test_always_all_patterns() {
623 let expected_always = ["always", "always allow", "trust", "trust it", "allow all"];
624 assert_eq!(
625 expected_always.len(),
626 5,
627 "Must have exactly 5 always patterns"
628 );
629 for pattern in &expected_always {
630 assert!(
631 matches!(
632 match_permission_command(pattern),
633 MatchResult::PermissionReply {
634 reply: PermissionReply::Always,
635 ..
636 }
637 ),
638 "Pattern '{}' should match Always",
639 pattern
640 );
641 }
642 }
643
644 #[test]
645 fn test_reject_all_patterns() {
646 let expected_reject = [
647 "reject",
648 "deny",
649 "no",
650 "nope",
651 "cancel",
652 "stop",
653 "don't",
654 "do not",
655 "refuse",
656 "block",
657 "skip",
658 "decline",
659 "dismiss",
660 "not allowed",
661 "nah",
662 "don't do it",
663 ];
664 assert_eq!(
665 expected_reject.len(),
666 16,
667 "Must have exactly 16 reject patterns"
668 );
669 for pattern in &expected_reject {
670 assert!(
671 matches!(
672 match_permission_command(pattern),
673 MatchResult::PermissionReply {
674 reply: PermissionReply::Reject,
675 ..
676 }
677 ),
678 "Pattern '{}' should match Reject",
679 pattern
680 );
681 }
682 }
683
684 #[test]
685 fn test_always_takes_priority_over_once() {
686 assert!(matches!(
688 match_permission_command("always allow"),
689 MatchResult::PermissionReply {
690 reply: PermissionReply::Always,
691 ..
692 }
693 ));
694 }
695
696 #[test]
697 fn test_not_allowed_exact_reject() {
698 let result = match_permission_command("not allowed");
700 match result {
701 MatchResult::PermissionReply {
702 reply: PermissionReply::Reject,
703 message,
704 } => {
705 assert_eq!(message, None, "'not allowed' should have no message");
706 }
707 _ => panic!("Expected reject"),
708 }
709 }
710
711 #[test]
712 fn test_question_label_in_text() {
713 let q = make_question(&["Continue", "Cancel"], true);
714 let result = match_question_answer("I want to continue", &q);
716 assert!(
717 matches!(result, MatchResult::QuestionAnswer { answers } if answers[0] == vec!["Continue"])
718 );
719 }
720
721 #[test]
722 fn test_question_numeric_option_2() {
723 let q = make_question(&["Alpha", "Beta", "Gamma"], true);
724 let result = match_question_answer("option 2", &q);
725 assert!(
726 matches!(result, MatchResult::QuestionAnswer { answers } if answers[0] == vec!["Beta"])
727 );
728 }
729
730 #[test]
731 fn test_question_numeric_word_second() {
732 let q = make_question(&["Alpha", "Beta"], true);
733 let result = match_question_answer("second", &q);
734 assert!(
735 matches!(result, MatchResult::QuestionAnswer { answers } if answers[0] == vec!["Beta"])
736 );
737 }
738
739 #[test]
740 fn test_question_reject_never_mind() {
741 let q = make_question(&["Yes", "No"], true);
742 assert_eq!(
743 match_question_answer("never mind", &q),
744 MatchResult::QuestionReject
745 );
746 }
747
748 #[test]
749 fn test_question_empty_questions_no_match() {
750 let q = QuestionRequest {
751 id: "test".to_string(),
752 questions: vec![],
753 };
754 assert_eq!(match_question_answer("yes", &q), MatchResult::NoMatch);
755 }
756
757 #[test]
761 fn test_normalize_extra_whitespace() {
762 assert_eq!(normalize(" hello world "), "hello world");
763 }
764
765 #[test]
767 fn test_normalize_mixed_case() {
768 assert_eq!(normalize("ALLOW"), "allow");
769 assert_eq!(normalize("AlLoW OnCe"), "allow once");
770 }
771
772 #[test]
774 fn test_normalize_multiple_trailing_punctuation() {
775 assert_eq!(normalize("yes!?"), "yes");
776 assert_eq!(normalize("ok..."), "ok");
777 }
778
779 #[test]
781 fn test_normalize_internal_punctuation_preserved() {
782 let result = normalize("no, try again");
784 assert_eq!(result, "no, try again");
785 }
786
787 #[test]
789 fn test_once_yep() {
790 assert!(matches!(
791 match_permission_command("yep"),
792 MatchResult::PermissionReply {
793 reply: PermissionReply::Once,
794 ..
795 }
796 ));
797 }
798
799 #[test]
800 fn test_once_go_ahead() {
801 assert!(matches!(
802 match_permission_command("go ahead"),
803 MatchResult::PermissionReply {
804 reply: PermissionReply::Once,
805 ..
806 }
807 ));
808 }
809
810 #[test]
811 fn test_once_do_it() {
812 assert!(matches!(
813 match_permission_command("do it"),
814 MatchResult::PermissionReply {
815 reply: PermissionReply::Once,
816 ..
817 }
818 ));
819 }
820
821 #[test]
822 fn test_once_run_it() {
823 assert!(matches!(
824 match_permission_command("run it"),
825 MatchResult::PermissionReply {
826 reply: PermissionReply::Once,
827 ..
828 }
829 ));
830 }
831
832 #[test]
833 fn test_once_go_for_it() {
834 assert!(matches!(
835 match_permission_command("go for it"),
836 MatchResult::PermissionReply {
837 reply: PermissionReply::Once,
838 ..
839 }
840 ));
841 }
842
843 #[test]
844 fn test_once_permit() {
845 assert!(matches!(
846 match_permission_command("permit"),
847 MatchResult::PermissionReply {
848 reply: PermissionReply::Once,
849 ..
850 }
851 ));
852 }
853
854 #[test]
855 fn test_once_allow_once() {
856 assert!(matches!(
857 match_permission_command("allow once"),
858 MatchResult::PermissionReply {
859 reply: PermissionReply::Once,
860 ..
861 }
862 ));
863 }
864
865 #[test]
866 fn test_once_allow_it() {
867 assert!(matches!(
868 match_permission_command("allow it"),
869 MatchResult::PermissionReply {
870 reply: PermissionReply::Once,
871 ..
872 }
873 ));
874 }
875
876 #[test]
877 fn test_once_allow_this() {
878 assert!(matches!(
879 match_permission_command("allow this"),
880 MatchResult::PermissionReply {
881 reply: PermissionReply::Once,
882 ..
883 }
884 ));
885 }
886
887 #[test]
888 fn test_once_approve_once() {
889 assert!(matches!(
890 match_permission_command("approve once"),
891 MatchResult::PermissionReply {
892 reply: PermissionReply::Once,
893 ..
894 }
895 ));
896 }
897
898 #[test]
899 fn test_once_approve_it() {
900 assert!(matches!(
901 match_permission_command("approve it"),
902 MatchResult::PermissionReply {
903 reply: PermissionReply::Once,
904 ..
905 }
906 ));
907 }
908
909 #[test]
910 fn test_once_approve_this() {
911 assert!(matches!(
912 match_permission_command("approve this"),
913 MatchResult::PermissionReply {
914 reply: PermissionReply::Once,
915 ..
916 }
917 ));
918 }
919
920 #[test]
922 fn test_reject_stop() {
923 assert!(matches!(
924 match_permission_command("stop"),
925 MatchResult::PermissionReply {
926 reply: PermissionReply::Reject,
927 ..
928 }
929 ));
930 }
931
932 #[test]
933 fn test_reject_dont() {
934 assert!(matches!(
935 match_permission_command("don't"),
936 MatchResult::PermissionReply {
937 reply: PermissionReply::Reject,
938 ..
939 }
940 ));
941 }
942
943 #[test]
944 fn test_reject_do_not() {
945 assert!(matches!(
946 match_permission_command("do not"),
947 MatchResult::PermissionReply {
948 reply: PermissionReply::Reject,
949 ..
950 }
951 ));
952 }
953
954 #[test]
955 fn test_reject_refuse() {
956 assert!(matches!(
957 match_permission_command("refuse"),
958 MatchResult::PermissionReply {
959 reply: PermissionReply::Reject,
960 ..
961 }
962 ));
963 }
964
965 #[test]
966 fn test_reject_block() {
967 assert!(matches!(
968 match_permission_command("block"),
969 MatchResult::PermissionReply {
970 reply: PermissionReply::Reject,
971 ..
972 }
973 ));
974 }
975
976 #[test]
977 fn test_reject_skip() {
978 assert!(matches!(
979 match_permission_command("skip"),
980 MatchResult::PermissionReply {
981 reply: PermissionReply::Reject,
982 ..
983 }
984 ));
985 }
986
987 #[test]
988 fn test_reject_decline() {
989 assert!(matches!(
990 match_permission_command("decline"),
991 MatchResult::PermissionReply {
992 reply: PermissionReply::Reject,
993 ..
994 }
995 ));
996 }
997
998 #[test]
999 fn test_reject_dismiss() {
1000 assert!(matches!(
1001 match_permission_command("dismiss"),
1002 MatchResult::PermissionReply {
1003 reply: PermissionReply::Reject,
1004 ..
1005 }
1006 ));
1007 }
1008
1009 #[test]
1010 fn test_reject_nah() {
1011 assert!(matches!(
1012 match_permission_command("nah"),
1013 MatchResult::PermissionReply {
1014 reply: PermissionReply::Reject,
1015 ..
1016 }
1017 ));
1018 }
1019
1020 #[test]
1021 fn test_reject_dont_do_it() {
1022 assert!(matches!(
1023 match_permission_command("don't do it"),
1024 MatchResult::PermissionReply {
1025 reply: PermissionReply::Reject,
1026 ..
1027 }
1028 ));
1029 }
1030
1031 #[test]
1033 fn test_reject_with_message_deny_prefix() {
1034 let result = match_permission_command("deny, not safe");
1035 match result {
1036 MatchResult::PermissionReply {
1037 reply: PermissionReply::Reject,
1038 message,
1039 } => {
1040 assert_eq!(message, Some("not safe".to_string()));
1041 }
1042 _ => panic!("Expected reject with message"),
1043 }
1044 }
1045
1046 #[test]
1047 fn test_reject_with_message_cancel_prefix() {
1048 let result = match_permission_command("cancel, wrong command");
1049 match result {
1050 MatchResult::PermissionReply {
1051 reply: PermissionReply::Reject,
1052 message,
1053 } => {
1054 assert_eq!(message, Some("wrong command".to_string()));
1055 }
1056 _ => panic!("Expected reject with message"),
1057 }
1058 }
1059
1060 #[test]
1061 fn test_reject_with_message_no_space_separator() {
1062 let result = match_permission_command("no try again");
1064 match result {
1065 MatchResult::PermissionReply {
1066 reply: PermissionReply::Reject,
1067 message,
1068 } => {
1069 assert_eq!(message, Some("try again".to_string()));
1070 }
1071 _ => panic!("Expected reject with message"),
1072 }
1073 }
1074
1075 #[test]
1077 fn test_once_case_insensitive() {
1078 assert!(matches!(
1079 match_permission_command("YES"),
1080 MatchResult::PermissionReply {
1081 reply: PermissionReply::Once,
1082 ..
1083 }
1084 ));
1085 assert!(matches!(
1086 match_permission_command("Allow"),
1087 MatchResult::PermissionReply {
1088 reply: PermissionReply::Once,
1089 ..
1090 }
1091 ));
1092 }
1093
1094 #[test]
1095 fn test_always_case_insensitive() {
1096 assert!(matches!(
1097 match_permission_command("ALWAYS"),
1098 MatchResult::PermissionReply {
1099 reply: PermissionReply::Always,
1100 ..
1101 }
1102 ));
1103 assert!(matches!(
1104 match_permission_command("Trust"),
1105 MatchResult::PermissionReply {
1106 reply: PermissionReply::Always,
1107 ..
1108 }
1109 ));
1110 }
1111
1112 #[test]
1113 fn test_reject_case_insensitive() {
1114 assert!(matches!(
1115 match_permission_command("NO"),
1116 MatchResult::PermissionReply {
1117 reply: PermissionReply::Reject,
1118 ..
1119 }
1120 ));
1121 assert!(matches!(
1122 match_permission_command("Deny"),
1123 MatchResult::PermissionReply {
1124 reply: PermissionReply::Reject,
1125 ..
1126 }
1127 ));
1128 }
1129
1130 #[test]
1132 fn test_question_reject_cancel() {
1133 let q = make_question(&["Yes", "No"], true);
1134 assert_eq!(
1135 match_question_answer("cancel", &q),
1136 MatchResult::QuestionReject
1137 );
1138 }
1139
1140 #[test]
1142 fn test_question_reject_nevermind() {
1143 let q = make_question(&["Yes", "No"], true);
1144 assert_eq!(
1145 match_question_answer("nevermind", &q),
1146 MatchResult::QuestionReject
1147 );
1148 }
1149
1150 #[test]
1152 fn test_question_reject_none() {
1153 let q = make_question(&["Yes", "No"], true);
1154 assert_eq!(
1155 match_question_answer("none", &q),
1156 MatchResult::QuestionReject
1157 );
1158 }
1159
1160 #[test]
1162 fn test_question_numeric_word_two() {
1163 let q = make_question(&["Alpha", "Beta", "Gamma"], true);
1164 let result = match_question_answer("two", &q);
1165 assert!(
1166 matches!(result, MatchResult::QuestionAnswer { answers } if answers[0] == vec!["Beta"])
1167 );
1168 }
1169
1170 #[test]
1172 fn test_question_numeric_option_3() {
1173 let q = make_question(&["Alpha", "Beta", "Gamma"], true);
1174 let result = match_question_answer("option 3", &q);
1175 assert!(
1176 matches!(result, MatchResult::QuestionAnswer { answers } if answers[0] == vec!["Gamma"])
1177 );
1178 }
1179
1180 #[test]
1182 fn test_question_numeric_out_of_range_with_custom() {
1183 let q = make_question(&["Alpha", "Beta"], true);
1184 let result = match_question_answer("option 5", &q);
1186 assert!(matches!(result, MatchResult::QuestionAnswer { .. }));
1187 }
1188
1189 #[test]
1191 fn test_question_numeric_out_of_range_no_custom() {
1192 let q = make_question(&["Alpha", "Beta"], false);
1193 let result = match_question_answer("option 5", &q);
1195 assert_eq!(result, MatchResult::NoMatch);
1196 }
1197
1198 #[test]
1200 fn test_question_exact_label_case_insensitive() {
1201 let q = make_question(&["Continue", "Cancel", "Retry"], false);
1202 let result = match_question_answer("CONTINUE", &q);
1203 assert!(
1204 matches!(result, MatchResult::QuestionAnswer { answers } if answers[0] == vec!["Continue"])
1205 );
1206 }
1207
1208 #[test]
1210 fn test_question_custom_answer_preserves_text() {
1211 let q = make_question(&["Yes", "No"], true);
1212 let result = match_question_answer(" My Custom Answer ", &q);
1213 match result {
1214 MatchResult::QuestionAnswer { answers } => {
1215 assert_eq!(answers[0], vec!["My Custom Answer"]);
1217 }
1218 _ => panic!("Expected QuestionAnswer"),
1219 }
1220 }
1221
1222 #[test]
1224 fn test_no_match_random_text() {
1225 assert_eq!(
1226 match_permission_command("the quick brown fox"),
1227 MatchResult::NoMatch
1228 );
1229 }
1230
1231 #[test]
1233 fn test_no_match_whitespace_only() {
1234 assert_eq!(match_permission_command(" "), MatchResult::NoMatch);
1235 }
1236}