1mod audit;
4mod benchmark;
5mod chain;
6mod ci;
7mod deploy;
8mod deploy_safe;
9mod doctor;
10mod fuzz;
11mod gas;
12mod import;
13mod install_hook;
14mod invariant;
15mod notify;
16mod plugins;
17mod report;
18mod sbom;
19mod scan;
20mod security;
21mod simulate;
22mod upgrade_check;
23mod verify;
24mod watch;
25
26use clap::{Parser, Subcommand};
27use std::path::PathBuf;
28
29#[derive(Parser, Debug)]
31#[command(
32 name = "forge-guard",
33 version,
34 about = "Pre-deployment smart contract auditing framework for Foundry",
35 long_about = "The most comprehensive pre-deployment smart contract auditing framework for Foundry.\n\nTransforms security auditing from an optional step into a mandatory pre-deployment process.\nBlocks unsafe deployments by default while providing detailed vulnerability reports.",
36 author
37)]
38pub struct Cli {
39 #[command(subcommand)]
40 pub command: Commands,
41}
42
43impl Cli {
44 pub fn from_env() -> Self {
46 Self::parse()
47 }
48
49 pub fn run(&self) -> anyhow::Result<()> {
51 use anyhow::Context;
52 match &self.command {
53 Commands::Audit(args) => audit::run(args).context("Audit failed"),
54 Commands::Deploy(args) => deploy::run(args).context("Deploy failed"),
55 Commands::DeploySafe(args) => deploy_safe::run(args).context("Safe deploy failed"),
56 Commands::Fuzz(args) => fuzz::run(args).context("Fuzzing failed"),
57 Commands::Invariant(args) => invariant::run(args).context("Invariant test failed"),
58 Commands::Simulate(args) => simulate::run(args).context("Simulation failed"),
59 Commands::Gas(args) => gas::run(args).context("Gas analysis failed"),
60 Commands::Report(args) => report::run(args).context("Report generation failed"),
61 Commands::Verify(args) => verify::run(args).context("Verification failed"),
62 Commands::Doctor(args) => doctor::run(args).context("Doctor analysis failed"),
63 Commands::Watch(args) => watch::run(args).context("Watch failed"),
64 Commands::Ci(args) => ci::run(args).context("CI generation failed"),
65 Commands::Benchmark(args) => benchmark::run(args).context("Benchmark failed"),
66 Commands::Scan(args) => scan::run(args).context("Scan failed"),
67 Commands::UpgradeCheck(args) => {
68 upgrade_check::run(args).context("Upgrade check failed")
69 }
70 Commands::Plugins(args) => plugins::run(args).context("Plugin operation failed"),
71 Commands::Chain(args) => chain::run(args).context("Chain operation failed"),
72 Commands::Sbom(args) => sbom::run(args).context("SBOM generation failed"),
73 Commands::InstallHook(args) => install_hook::run(args).context("Install hook failed"),
74 Commands::Security(args) => security::run(args).context("Security operation failed"),
75 Commands::Import(args) => import::run(args).context("Import failed"),
76 Commands::Notify(args) => notify::run(args).context("Notification failed"),
77 }
78 }
79}
80
81#[derive(Subcommand, Debug)]
83pub enum Commands {
84 Audit(AuditArgs),
86 Deploy(DeployArgs),
88 #[command(name = "deploy-safe")]
90 DeploySafe(DeploySafeArgs),
91 Fuzz(FuzzArgs),
93 Invariant(InvariantArgs),
95 Simulate(SimulateArgs),
97 Gas(GasArgs),
99 Report(ReportArgs),
101 Verify(VerifyArgs),
103 Doctor(DoctorArgs),
105 Watch(WatchArgs),
107 Ci(CiArgs),
109 Benchmark(BenchmarkArgs),
111 Scan(ScanArgs),
113 #[command(name = "upgrade-check")]
115 UpgradeCheck(UpgradeCheckArgs),
116 Plugins(PluginArgs),
118 Chain(ChainArgs),
120 Sbom(SbomArgs),
122 #[command(name = "install-hook")]
124 InstallHook(install_hook::InstallHookArgs),
125
126 Security(SecurityArgs),
128 Import(ImportArgs),
130 Notify(NotifyArgs),
132}
133
134#[derive(Debug, Default, clap::Args)]
138pub struct SharedFlags {
139 #[arg(long, global = true, default_value = "ethereum")]
141 pub chain: String,
142
143 #[arg(long, global = true, default_value = ".")]
145 pub project: PathBuf,
146
147 #[arg(long, global = true)]
149 pub json: bool,
150
151 #[arg(long, global = true)]
153 pub markdown: bool,
154
155 #[arg(long, global = true)]
157 pub html: bool,
158
159 #[arg(long, global = true)]
161 pub strict: bool,
162
163 #[arg(long, global = true)]
165 pub offline: bool,
166
167 #[arg(long, global = true)]
169 pub production: bool,
170
171 #[arg(long, global = true)]
173 pub report: bool,
174
175 #[arg(long, global = true, default_value = "4")]
177 pub parallelism: usize,
178}
179
180#[derive(Debug, clap::Args)]
183pub struct AuditArgs {
184 #[command(flatten)]
185 pub shared: SharedFlags,
186
187 #[arg(long, short)]
189 pub full: bool,
190
191 #[arg(long)]
193 pub quick: bool,
194
195 #[arg(long)]
197 pub summary: bool,
198
199 #[arg(long)]
201 pub ai: bool,
202
203 #[arg(long, default_value = "openai")]
205 pub ai_provider: String,
206
207 #[arg(long, default_value = "gpt-5")]
209 pub ai_model: String,
210
211 #[arg(long)]
213 pub ai_api_key: Option<String>,
214
215 #[arg(long)]
217 pub ollama_endpoint: Option<String>,
218
219 #[arg(long)]
221 pub ai_full: bool,
222
223 #[arg(long)]
225 pub exploit: bool,
226
227 #[arg(long)]
229 pub gas: bool,
230
231 #[arg(long)]
233 pub all_chains: bool,
234
235 #[arg(long, default_value = "4")]
237 pub max_parallel_chains: usize,
238
239 #[arg(long, default_value = "src")]
241 pub sources: String,
242
243 #[arg(long)]
245 pub exclude: Option<String>,
246
247 #[arg(long)]
249 pub template: Option<String>,
250
251 #[arg(long)]
253 pub list_templates: bool,
254
255 #[arg(long)]
257 pub suppressions: Option<PathBuf>,
258
259 #[arg(long)]
261 pub show_suppressed: bool,
262
263 #[arg(long)]
265 pub generate_suppressions: bool,
266
267 #[arg(long)]
269 pub notify: bool,
270}
271
272#[derive(Debug, clap::Args)]
273pub struct DeployArgs {
274 #[command(flatten)]
275 pub shared: SharedFlags,
276
277 pub contract: Option<String>,
279
280 #[arg(long)]
282 pub force: bool,
283
284 #[arg(long)]
286 pub args: Option<String>,
287
288 #[arg(long)]
290 pub salt: Option<String>,
291
292 #[arg(long)]
294 pub verify: bool,
295
296 #[arg(long)]
298 pub notify: bool,
299}
300
301#[derive(Debug, clap::Args)]
302pub struct DeploySafeArgs {
303 #[command(flatten)]
304 pub shared: SharedFlags,
305
306 pub contract: Option<String>,
308
309 #[arg(long)]
311 pub args: Option<String>,
312
313 #[arg(long)]
315 pub verify: bool,
316
317 #[arg(long)]
319 pub notify: bool,
320}
321
322#[derive(Debug, clap::Args)]
323pub struct FuzzArgs {
324 #[command(flatten)]
325 pub shared: SharedFlags,
326
327 #[arg(long, default_value = "10000")]
329 pub runs: u32,
330
331 #[arg(long)]
333 pub seed: Option<u64>,
334
335 #[arg(long)]
337 pub test: Option<String>,
338
339 pub contract: Option<String>,
341}
342
343#[derive(Debug, clap::Args)]
344pub struct InvariantArgs {
345 #[command(flatten)]
346 pub shared: SharedFlags,
347
348 #[arg(long, default_value = "1000")]
350 pub runs: u32,
351
352 #[arg(long, default_value = "100")]
354 pub depth: u32,
355
356 pub contract: Option<String>,
358
359 #[arg(long)]
361 pub fail_on_revert: bool,
362}
363
364#[derive(Debug, clap::Args)]
365pub struct SimulateArgs {
366 #[command(flatten)]
367 pub shared: SharedFlags,
368
369 #[arg(long, default_value = "100")]
371 pub blocks: u32,
372
373 #[arg(long)]
375 pub deployer: Option<String>,
376
377 #[arg(long)]
379 pub mev: bool,
380
381 pub contract: Option<String>,
383}
384
385#[derive(Debug, clap::Args)]
386pub struct GasArgs {
387 #[command(flatten)]
388 pub shared: SharedFlags,
389
390 pub contract: Option<String>,
392
393 #[arg(long)]
395 pub diff: Option<String>,
396
397 #[arg(long)]
399 pub all: bool,
400
401 #[arg(long, default_value = "50000")]
403 pub warn_threshold: u64,
404}
405
406#[derive(Debug, clap::Args)]
407pub struct ReportArgs {
408 #[command(flatten)]
409 pub shared: SharedFlags,
410
411 pub input: Option<PathBuf>,
413
414 #[arg(long, default_value = "markdown")]
416 pub format: String,
417
418 #[arg(long)]
420 pub output: Option<PathBuf>,
421
422 #[arg(long)]
424 pub exploit_paths: bool,
425
426 #[arg(long)]
428 pub summary: bool,
429}
430
431#[derive(Debug, clap::Args)]
432pub struct VerifyArgs {
433 #[command(flatten)]
434 pub shared: SharedFlags,
435
436 pub address: Option<String>,
438
439 pub name: Option<String>,
441
442 #[arg(long)]
444 pub api_key: Option<String>,
445
446 #[arg(long)]
448 pub constructor_args: Option<String>,
449
450 #[arg(long)]
452 pub all: bool,
453}
454
455#[derive(Debug, clap::Args)]
456pub struct DoctorArgs {
457 #[command(flatten)]
458 pub shared: SharedFlags,
459
460 #[arg(long)]
462 pub fix: bool,
463
464 #[arg(long, short)]
466 pub verbose: bool,
467
468 #[arg(long)]
470 pub check: Option<String>,
471
472 #[arg(long)]
474 pub sync: bool,
475
476 #[arg(long)]
478 pub dry_run: bool,
479
480 #[arg(long)]
482 pub diff: bool,
483}
484
485#[derive(Debug, clap::Args)]
486pub struct WatchArgs {
487 #[command(flatten)]
488 pub shared: SharedFlags,
489
490 #[arg(long, default_value = "src")]
492 pub dirs: String,
493
494 #[arg(long, default_value = "500")]
496 pub debounce_ms: u64,
497
498 #[arg(long)]
500 pub exclude: Option<String>,
501
502 #[arg(long)]
504 pub full: bool,
505}
506
507#[derive(Debug, clap::Args)]
508pub struct CiArgs {
509 #[command(flatten)]
510 pub shared: SharedFlags,
511
512 #[arg(long, default_value = "github")]
514 pub platform: String,
515
516 #[arg(long, default_value = ".github/workflows")]
518 pub output: PathBuf,
519
520 #[arg(long)]
522 pub include_deploy: bool,
523
524 #[arg(long)]
526 pub overwrite: bool,
527}
528
529#[derive(Debug, clap::Args)]
530pub struct BenchmarkArgs {
531 #[command(flatten)]
532 pub shared: SharedFlags,
533
534 #[arg(long, default_value = "10")]
536 pub iterations: u32,
537
538 #[arg(long)]
540 pub compare: Option<PathBuf>,
541
542 #[arg(long)]
544 pub save: Option<PathBuf>,
545
546 #[arg(long)]
548 pub module: Option<String>,
549
550 #[arg(long, default_value = "3")]
552 pub warmup: u32,
553}
554
555#[derive(Debug, clap::Args)]
556pub struct SbomArgs {
557 #[command(flatten)]
558 pub shared: SharedFlags,
559
560 #[arg(long, default_value = "cyclonedx")]
562 pub format: String,
563
564 #[arg(long, short)]
566 pub output: Option<PathBuf>,
567
568 #[arg(long)]
570 pub ci: bool,
571}
572
573#[derive(Debug, clap::Args)]
574pub struct ScanArgs {
575 #[command(flatten)]
576 pub shared: SharedFlags,
577
578 #[arg(long, default_value = "1")]
580 pub depth: u32,
581
582 #[arg(long)]
584 pub update: bool,
585
586 #[arg(long)]
588 pub vulnerable_only: bool,
589
590 #[arg(long)]
592 pub fail_fast: bool,
593}
594
595#[derive(Debug, clap::Args)]
596pub struct UpgradeCheckArgs {
597 #[command(flatten)]
598 pub shared: SharedFlags,
599
600 pub proxy: Option<String>,
602
603 pub implementation: Option<String>,
605
606 #[arg(long)]
608 pub all: bool,
609
610 #[arg(long)]
612 pub storage_collision: bool,
613
614 #[arg(long)]
616 pub uups: bool,
617}
618
619#[derive(Debug, clap::Args)]
620pub struct PluginArgs {
621 #[command(flatten)]
622 pub shared: SharedFlags,
623
624 #[command(subcommand)]
626 pub action: Option<PluginAction>,
627}
628
629#[derive(Debug, Subcommand)]
630pub enum PluginAction {
631 List,
633 Install {
635 name: String,
636 source: Option<String>,
637 },
638 Remove { name: String },
640 Enable { name: String },
642 Disable { name: String },
644 New { name: String },
646}
647
648#[derive(Debug, clap::Args)]
649pub struct ChainArgs {
650 #[command(flatten)]
651 pub shared: SharedFlags,
652
653 #[command(subcommand)]
655 pub action: Option<ChainAction>,
656}
657
658#[derive(Debug, Subcommand)]
659pub enum ChainAction {
660 List,
662 Info { chain: String },
664 Add {
666 name: String,
667 rpc_url: Option<String>,
668 chain_id: Option<u64>,
669 },
670 Remove { name: String },
672 Test {
674 chain: String,
675 rpc_url: Option<String>,
676 },
677}
678
679#[derive(Debug, clap::Args)]
680pub struct SecurityArgs {
681 #[command(flatten)]
682 pub shared: SharedFlags,
683
684 #[command(subcommand)]
686 pub action: Option<SecurityAction>,
687}
688
689#[derive(Debug, Subcommand)]
690pub enum SecurityAction {
691 Config,
693 Threshold { score: u8 },
695 Enable { check: String },
697 Disable { check: String },
699 List,
701 Info { check: String },
703}
704
705#[derive(Debug, clap::Args)]
708pub struct ImportArgs {
709 #[command(flatten)]
710 pub shared: SharedFlags,
711
712 #[arg(long, default_value = "slither")]
714 pub from: String,
715
716 pub input: Option<PathBuf>,
718
719 #[arg(long)]
721 pub findings: Option<PathBuf>,
722
723 #[arg(long, short)]
725 pub output: Option<PathBuf>,
726}
727
728#[derive(Debug, clap::Args)]
729pub struct NotifyArgs {
730 #[command(flatten)]
731 pub shared: SharedFlags,
732
733 #[arg(long)]
735 pub webhook: Option<String>,
736
737 #[arg(long)]
739 pub kind: Option<String>,
740
741 #[arg(long)]
743 pub findings: Option<PathBuf>,
744
745 #[arg(long, default_value = "Forge Guard Audit")]
747 pub title: String,
748
749 #[arg(long)]
751 pub message: Option<String>,
752
753 #[arg(long)]
755 pub severity: Option<String>,
756
757 #[arg(long)]
759 pub on_critical: bool,
760
761 #[arg(long)]
763 pub on_high: bool,
764
765 #[arg(long)]
767 pub dry_run: bool,
768}
769
770#[cfg(test)]
771mod tests {
772 use super::*;
773 use clap::Parser;
774
775 #[test]
776 fn test_cli_parse_audit() {
777 let cli = Cli::try_parse_from(["forge-guard", "audit"]).unwrap();
778 assert!(matches!(cli.command, Commands::Audit(_)));
779 }
780
781 #[test]
782 fn test_cli_parse_deploy() {
783 let cli = Cli::try_parse_from(["forge-guard", "deploy"]).unwrap();
784 assert!(matches!(cli.command, Commands::Deploy(_)));
785 }
786
787 #[test]
788 fn test_cli_parse_deploy_safe() {
789 let cli = Cli::try_parse_from(["forge-guard", "deploy-safe"]).unwrap();
790 assert!(matches!(cli.command, Commands::DeploySafe(_)));
791 }
792
793 #[test]
794 fn test_cli_parse_fuzz() {
795 let cli = Cli::try_parse_from(["forge-guard", "fuzz"]).unwrap();
796 assert!(matches!(cli.command, Commands::Fuzz(_)));
797 }
798
799 #[test]
800 fn test_cli_parse_invariant() {
801 let cli = Cli::try_parse_from(["forge-guard", "invariant"]).unwrap();
802 assert!(matches!(cli.command, Commands::Invariant(_)));
803 }
804
805 #[test]
806 fn test_cli_parse_simulate() {
807 let cli = Cli::try_parse_from(["forge-guard", "simulate"]).unwrap();
808 assert!(matches!(cli.command, Commands::Simulate(_)));
809 }
810
811 #[test]
812 fn test_cli_parse_gas() {
813 let cli = Cli::try_parse_from(["forge-guard", "gas"]).unwrap();
814 assert!(matches!(cli.command, Commands::Gas(_)));
815 }
816
817 #[test]
818 fn test_cli_parse_report() {
819 let cli = Cli::try_parse_from(["forge-guard", "report"]).unwrap();
820 assert!(matches!(cli.command, Commands::Report(_)));
821 }
822
823 #[test]
824 fn test_cli_parse_verify() {
825 let cli = Cli::try_parse_from(["forge-guard", "verify"]).unwrap();
826 assert!(matches!(cli.command, Commands::Verify(_)));
827 }
828
829 #[test]
830 fn test_cli_parse_doctor() {
831 let cli = Cli::try_parse_from(["forge-guard", "doctor"]).unwrap();
832 assert!(matches!(cli.command, Commands::Doctor(_)));
833 }
834
835 #[test]
836 fn test_cli_parse_watch() {
837 let cli = Cli::try_parse_from(["forge-guard", "watch"]).unwrap();
838 assert!(matches!(cli.command, Commands::Watch(_)));
839 }
840
841 #[test]
842 fn test_cli_parse_ci() {
843 let cli = Cli::try_parse_from(["forge-guard", "ci"]).unwrap();
844 assert!(matches!(cli.command, Commands::Ci(_)));
845 }
846
847 #[test]
848 fn test_cli_parse_benchmark() {
849 let cli = Cli::try_parse_from(["forge-guard", "benchmark"]).unwrap();
850 assert!(matches!(cli.command, Commands::Benchmark(_)));
851 }
852
853 #[test]
854 fn test_cli_parse_scan() {
855 let cli = Cli::try_parse_from(["forge-guard", "scan"]).unwrap();
856 assert!(matches!(cli.command, Commands::Scan(_)));
857 }
858
859 #[test]
860 fn test_cli_parse_upgrade_check() {
861 let cli = Cli::try_parse_from(["forge-guard", "upgrade-check"]).unwrap();
862 assert!(matches!(cli.command, Commands::UpgradeCheck(_)));
863 }
864
865 #[test]
866 fn test_cli_parse_plugins() {
867 let cli = Cli::try_parse_from(["forge-guard", "plugins"]).unwrap();
868 assert!(matches!(cli.command, Commands::Plugins(_)));
869 }
870
871 #[test]
872 fn test_cli_parse_chain() {
873 let cli = Cli::try_parse_from(["forge-guard", "chain"]).unwrap();
874 assert!(matches!(cli.command, Commands::Chain(_)));
875 }
876
877 #[test]
878 fn test_cli_parse_security() {
879 let cli = Cli::try_parse_from(["forge-guard", "security"]).unwrap();
880 assert!(matches!(cli.command, Commands::Security(_)));
881 }
882
883 #[test]
884 fn test_cli_parse_install_hook() {
885 let cli = Cli::try_parse_from(["forge-guard", "install-hook"]).unwrap();
886 assert!(matches!(cli.command, Commands::InstallHook(_)));
887 }
888
889 #[test]
890 fn test_cli_parse_install_hook_uninstall() {
891 let cli = Cli::try_parse_from(["forge-guard", "install-hook", "--uninstall"]).unwrap();
892 assert!(matches!(cli.command, Commands::InstallHook(_)));
893 }
894
895 #[test]
896 fn test_cli_parse_doctor_sync() {
897 let cli = Cli::try_parse_from(["forge-guard", "doctor", "--sync"]).unwrap();
898 if let Commands::Doctor(args) = cli.command {
899 assert!(args.sync);
900 } else {
901 panic!("Expected Doctor command");
902 }
903 }
904
905 #[test]
906 fn test_cli_parse_doctor_dry_run() {
907 let cli = Cli::try_parse_from(["forge-guard", "doctor", "--sync", "--dry-run"]).unwrap();
908 if let Commands::Doctor(args) = cli.command {
909 assert!(args.sync);
910 assert!(args.dry_run);
911 } else {
912 panic!("Expected Doctor command");
913 }
914 }
915
916 #[test]
917 fn test_cli_parse_audit_template() {
918 let cli = Cli::try_parse_from(["forge-guard", "audit", "--template", "defi"]).unwrap();
919 if let Commands::Audit(args) = cli.command {
920 assert_eq!(args.template.as_deref(), Some("defi"));
921 } else {
922 panic!("Expected Audit command");
923 }
924 }
925
926 #[test]
927 fn test_cli_parse_audit_list_templates() {
928 let cli = Cli::try_parse_from(["forge-guard", "audit", "--list-templates"]).unwrap();
929 if let Commands::Audit(args) = cli.command {
930 assert!(args.list_templates);
931 } else {
932 panic!("Expected Audit command");
933 }
934 }
935
936 #[test]
937 fn test_shared_flags_defaults() {
938 let cli = Cli::try_parse_from(["forge-guard", "audit"]).unwrap();
939 if let Commands::Audit(args) = cli.command {
940 assert_eq!(args.shared.chain, "ethereum");
941 assert!(!args.shared.json);
942 assert!(!args.shared.markdown);
943 assert!(!args.shared.strict);
944 assert!(!args.shared.offline);
945 assert!(!args.shared.production);
946 assert!(!args.shared.report);
947 assert_eq!(args.shared.parallelism, 4);
948 } else {
949 panic!("Expected Audit command");
950 }
951 }
952
953 #[test]
954 fn test_shared_flags_custom() {
955 let cli = Cli::try_parse_from([
956 "forge-guard",
957 "audit",
958 "--chain",
959 "base",
960 "--json",
961 "--strict",
962 "--offline",
963 "--production",
964 "--parallelism",
965 "8",
966 "--project",
967 "/tmp/project",
968 ])
969 .unwrap();
970 if let Commands::Audit(args) = cli.command {
971 assert_eq!(args.shared.chain, "base");
972 assert!(args.shared.json);
973 assert!(args.shared.strict);
974 assert!(args.shared.offline);
975 assert!(args.shared.production);
976 assert_eq!(args.shared.parallelism, 8);
977 assert_eq!(
978 args.shared.project,
979 std::path::PathBuf::from("/tmp/project")
980 );
981 } else {
982 panic!("Expected Audit command");
983 }
984 }
985
986 #[test]
987 fn test_audit_args_defaults() {
988 let cli = Cli::try_parse_from(["forge-guard", "audit"]).unwrap();
989 if let Commands::Audit(args) = cli.command {
990 assert!(!args.full);
991 assert!(!args.quick);
992 assert!(!args.summary);
993 assert!(!args.ai);
994 assert_eq!(args.ai_provider, "openai");
995 assert_eq!(args.ai_model, "gpt-5");
996 assert!(args.ai_api_key.is_none());
997 assert!(!args.exploit);
998 assert!(!args.gas);
999 assert!(!args.all_chains);
1000 assert_eq!(args.sources, "src");
1001 assert!(args.exclude.is_none());
1002 } else {
1003 panic!("Expected Audit command");
1004 }
1005 }
1006
1007 #[test]
1008 fn test_audit_args_full() {
1009 let cli = Cli::try_parse_from([
1010 "forge-guard",
1011 "audit",
1012 "--full",
1013 "--chain",
1014 "arbitrum",
1015 "--json",
1016 "--report",
1017 "--exploit",
1018 "--gas",
1019 ])
1020 .unwrap();
1021 if let Commands::Audit(args) = cli.command {
1022 assert!(args.full);
1023 assert!(args.exploit);
1024 assert!(args.gas);
1025 assert!(args.shared.json);
1026 assert!(args.shared.report);
1027 assert_eq!(args.shared.chain, "arbitrum");
1028 } else {
1029 panic!("Expected Audit command");
1030 }
1031 }
1032
1033 #[test]
1034 fn test_audit_ai_args() {
1035 let cli = Cli::try_parse_from([
1036 "forge-guard",
1037 "audit",
1038 "--ai",
1039 "--ai-provider",
1040 "claude",
1041 "--ai-model",
1042 "claude-5-sonnet-20260701",
1043 "--ai-full",
1044 ])
1045 .unwrap();
1046 if let Commands::Audit(args) = cli.command {
1047 assert!(args.ai);
1048 assert_eq!(args.ai_provider, "claude");
1049 assert_eq!(args.ai_model, "claude-5-sonnet-20260701");
1050 assert!(args.ai_full);
1051 } else {
1052 panic!("Expected Audit command");
1053 }
1054 }
1055
1056 #[test]
1057 fn test_deploy_args() {
1058 let cli = Cli::try_parse_from([
1059 "forge-guard",
1060 "deploy",
1061 "MyContract",
1062 "--force",
1063 "--salt",
1064 "0xabc",
1065 "--verify",
1066 "--args",
1067 "arg1,arg2",
1068 ])
1069 .unwrap();
1070 if let Commands::Deploy(args) = cli.command {
1071 assert_eq!(args.contract.as_deref(), Some("MyContract"));
1072 assert!(args.force);
1073 assert_eq!(args.salt.as_deref(), Some("0xabc"));
1074 assert!(args.verify);
1075 assert_eq!(args.args.as_deref(), Some("arg1,arg2"));
1076 } else {
1077 panic!("Expected Deploy command");
1078 }
1079 }
1080
1081 #[test]
1082 fn test_fuzz_args() {
1083 let cli = Cli::try_parse_from([
1084 "forge-guard",
1085 "fuzz",
1086 "--runs",
1087 "50000",
1088 "--seed",
1089 "42",
1090 "--test",
1091 "testFuzzDeposit",
1092 "Vault",
1093 ])
1094 .unwrap();
1095 if let Commands::Fuzz(args) = cli.command {
1096 assert_eq!(args.runs, 50_000);
1097 assert_eq!(args.seed, Some(42));
1098 assert_eq!(args.test.as_deref(), Some("testFuzzDeposit"));
1099 assert_eq!(args.contract.as_deref(), Some("Vault"));
1100 } else {
1101 panic!("Expected Fuzz command");
1102 }
1103 }
1104
1105 #[test]
1106 fn test_simulate_args() {
1107 let cli = Cli::try_parse_from([
1108 "forge-guard",
1109 "simulate",
1110 "MyContract",
1111 "--blocks",
1112 "200",
1113 "--mev",
1114 ])
1115 .unwrap();
1116 if let Commands::Simulate(args) = cli.command {
1117 assert_eq!(args.contract.as_deref(), Some("MyContract"));
1118 assert_eq!(args.blocks, 200);
1119 assert!(args.mev);
1120 } else {
1121 panic!("Expected Simulate command");
1122 }
1123 }
1124
1125 #[test]
1126 fn test_invariant_args() {
1127 let cli = Cli::try_parse_from([
1128 "forge-guard",
1129 "invariant",
1130 "--runs",
1131 "2000",
1132 "--depth",
1133 "150",
1134 "--fail-on-revert",
1135 ])
1136 .unwrap();
1137 if let Commands::Invariant(args) = cli.command {
1138 assert_eq!(args.runs, 2000);
1139 assert_eq!(args.depth, 150);
1140 assert!(args.fail_on_revert);
1141 } else {
1142 panic!("Expected Invariant command");
1143 }
1144 }
1145
1146 #[test]
1147 fn test_ci_args_defaults() {
1148 let cli = Cli::try_parse_from(["forge-guard", "ci"]).unwrap();
1149 if let Commands::Ci(args) = cli.command {
1150 assert_eq!(args.platform, "github");
1151 assert_eq!(args.output, std::path::PathBuf::from(".github/workflows"));
1152 assert!(!args.include_deploy);
1153 assert!(!args.overwrite);
1154 } else {
1155 panic!("Expected Ci command");
1156 }
1157 }
1158
1159 #[test]
1160 fn test_ci_args_custom() {
1161 let cli = Cli::try_parse_from([
1162 "forge-guard",
1163 "ci",
1164 "--platform",
1165 "gitlab",
1166 "--include-deploy",
1167 "--overwrite",
1168 "--output",
1169 ".gitlab",
1170 ])
1171 .unwrap();
1172 if let Commands::Ci(args) = cli.command {
1173 assert_eq!(args.platform, "gitlab");
1174 assert!(args.include_deploy);
1175 assert!(args.overwrite);
1176 assert_eq!(args.output, std::path::PathBuf::from(".gitlab"));
1177 } else {
1178 panic!("Expected Ci command");
1179 }
1180 }
1181
1182 #[test]
1183 fn test_benchmark_args() {
1184 let cli = Cli::try_parse_from([
1185 "forge-guard",
1186 "benchmark",
1187 "--iterations",
1188 "50",
1189 "--warmup",
1190 "5",
1191 "--module",
1192 "pattern_matching",
1193 ])
1194 .unwrap();
1195 if let Commands::Benchmark(args) = cli.command {
1196 assert_eq!(args.iterations, 50);
1197 assert_eq!(args.warmup, 5);
1198 assert_eq!(args.module.as_deref(), Some("pattern_matching"));
1199 } else {
1200 panic!("Expected Benchmark command");
1201 }
1202 }
1203
1204 #[test]
1205 fn test_gas_args() {
1206 let cli =
1207 Cli::try_parse_from(["forge-guard", "gas", "--all", "--warn-threshold", "100000"])
1208 .unwrap();
1209 if let Commands::Gas(args) = cli.command {
1210 assert!(args.all);
1211 assert_eq!(args.warn_threshold, 100000);
1212 assert!(args.contract.is_none());
1213 } else {
1214 panic!("Expected Gas command");
1215 }
1216 }
1217
1218 #[test]
1219 fn test_scan_args() {
1220 let cli = Cli::try_parse_from([
1221 "forge-guard",
1222 "scan",
1223 "--depth",
1224 "2",
1225 "--update",
1226 "--vulnerable-only",
1227 "--fail-fast",
1228 ])
1229 .unwrap();
1230 if let Commands::Scan(args) = cli.command {
1231 assert_eq!(args.depth, 2);
1232 assert!(args.update);
1233 assert!(args.vulnerable_only);
1234 assert!(args.fail_fast);
1235 } else {
1236 panic!("Expected Scan command");
1237 }
1238 }
1239
1240 #[test]
1241 fn test_upgrade_check_args() {
1242 let cli = Cli::try_parse_from([
1243 "forge-guard",
1244 "upgrade-check",
1245 "--storage-collision",
1246 "--uups",
1247 "--all",
1248 ])
1249 .unwrap();
1250 if let Commands::UpgradeCheck(args) = cli.command {
1251 assert!(args.storage_collision);
1252 assert!(args.uups);
1253 assert!(args.all);
1254 } else {
1255 panic!("Expected UpgradeCheck command");
1256 }
1257 }
1258
1259 #[test]
1260 fn test_doctor_args() {
1261 let cli = Cli::try_parse_from(["forge-guard", "doctor", "--fix", "--verbose"]).unwrap();
1262 if let Commands::Doctor(args) = cli.command {
1263 assert!(args.fix);
1264 assert!(args.verbose);
1265 assert!(args.check.is_none());
1266 } else {
1267 panic!("Expected Doctor command");
1268 }
1269 }
1270
1271 #[test]
1272 fn test_verify_args() {
1273 let cli = Cli::try_parse_from([
1274 "forge-guard",
1275 "verify",
1276 "0x1234",
1277 "MyContract",
1278 "--api-key",
1279 "test-key",
1280 "--all",
1281 ])
1282 .unwrap();
1283 if let Commands::Verify(args) = cli.command {
1284 assert_eq!(args.address.as_deref(), Some("0x1234"));
1285 assert_eq!(args.name.as_deref(), Some("MyContract"));
1286 assert_eq!(args.api_key.as_deref(), Some("test-key"));
1287 assert!(args.all);
1288 } else {
1289 panic!("Expected Verify command");
1290 }
1291 }
1292
1293 #[test]
1294 fn test_watch_args() {
1295 let cli = Cli::try_parse_from([
1296 "forge-guard",
1297 "watch",
1298 "--dirs",
1299 "src,test-contracts",
1300 "--debounce-ms",
1301 "1000",
1302 "--full",
1303 ])
1304 .unwrap();
1305 if let Commands::Watch(args) = cli.command {
1306 assert_eq!(args.dirs, "src,test-contracts");
1307 assert_eq!(args.debounce_ms, 1000);
1308 assert!(args.full);
1309 } else {
1310 panic!("Expected Watch command");
1311 }
1312 }
1313
1314 #[test]
1315 fn test_plugins_list() {
1316 let cli = Cli::try_parse_from(["forge-guard", "plugins", "list"]).unwrap();
1317 if let Commands::Plugins(args) = cli.command {
1318 assert!(matches!(args.action, Some(PluginAction::List)));
1319 } else {
1320 panic!("Expected Plugins command");
1321 }
1322 }
1323
1324 #[test]
1325 fn test_plugins_install() {
1326 let cli = Cli::try_parse_from(["forge-guard", "plugins", "install", "my-plugin"]).unwrap();
1327 if let Commands::Plugins(args) = cli.command {
1328 assert!(matches!(args.action, Some(PluginAction::Install { .. })));
1329 } else {
1330 panic!("Expected Plugins command");
1331 }
1332 }
1333
1334 #[test]
1335 fn test_plugins_remove() {
1336 let cli = Cli::try_parse_from(["forge-guard", "plugins", "remove", "bad-plugin"]).unwrap();
1337 if let Commands::Plugins(args) = cli.command {
1338 assert!(matches!(args.action, Some(PluginAction::Remove { .. })));
1339 } else {
1340 panic!("Expected Plugins command");
1341 }
1342 }
1343
1344 #[test]
1345 fn test_plugins_enable() {
1346 let cli = Cli::try_parse_from(["forge-guard", "plugins", "enable", "my-plugin"]).unwrap();
1347 if let Commands::Plugins(args) = cli.command {
1348 assert!(matches!(args.action, Some(PluginAction::Enable { .. })));
1349 } else {
1350 panic!("Expected Plugins command");
1351 }
1352 }
1353
1354 #[test]
1355 fn test_plugins_disable() {
1356 let cli = Cli::try_parse_from(["forge-guard", "plugins", "disable", "my-plugin"]).unwrap();
1357 if let Commands::Plugins(args) = cli.command {
1358 assert!(matches!(args.action, Some(PluginAction::Disable { .. })));
1359 } else {
1360 panic!("Expected Plugins command");
1361 }
1362 }
1363
1364 #[test]
1365 fn test_plugins_new() {
1366 let cli =
1367 Cli::try_parse_from(["forge-guard", "plugins", "new", "my-awesome-plugin"]).unwrap();
1368 if let Commands::Plugins(args) = cli.command {
1369 assert!(matches!(args.action, Some(PluginAction::New { .. })));
1370 } else {
1371 panic!("Expected Plugins command");
1372 }
1373 }
1374
1375 #[test]
1376 fn test_chain_list() {
1377 let cli = Cli::try_parse_from(["forge-guard", "chain", "list"]).unwrap();
1378 if let Commands::Chain(args) = cli.command {
1379 assert!(matches!(args.action, Some(ChainAction::List)));
1380 } else {
1381 panic!("Expected Chain command");
1382 }
1383 }
1384
1385 #[test]
1386 fn test_chain_info() {
1387 let cli = Cli::try_parse_from(["forge-guard", "chain", "info", "base"]).unwrap();
1388 if let Commands::Chain(args) = cli.command {
1389 assert!(matches!(args.action, Some(ChainAction::Info { .. })));
1390 } else {
1391 panic!("Expected Chain command");
1392 }
1393 }
1394
1395 #[test]
1396 fn test_chain_add() {
1397 let cli = Cli::try_parse_from([
1398 "forge-guard",
1399 "chain",
1400 "add",
1401 "my-chain",
1402 "https://rpc.my-chain.io",
1403 "99999",
1404 ])
1405 .unwrap();
1406 if let Commands::Chain(args) = cli.command {
1407 assert!(matches!(args.action, Some(ChainAction::Add { .. })));
1408 } else {
1409 panic!("Expected Chain command");
1410 }
1411 }
1412
1413 #[test]
1414 fn test_security_list() {
1415 let cli = Cli::try_parse_from(["forge-guard", "security", "list"]).unwrap();
1416 if let Commands::Security(args) = cli.command {
1417 assert!(matches!(args.action, Some(SecurityAction::List)));
1418 } else {
1419 panic!("Expected Security command");
1420 }
1421 }
1422
1423 #[test]
1424 fn test_security_threshold() {
1425 let cli = Cli::try_parse_from(["forge-guard", "security", "threshold", "85"]).unwrap();
1426 if let Commands::Security(args) = cli.command {
1427 assert!(matches!(
1428 args.action,
1429 Some(SecurityAction::Threshold { .. })
1430 ));
1431 } else {
1432 panic!("Expected Security command");
1433 }
1434 }
1435
1436 #[test]
1437 fn test_markdown_flag() {
1438 let cli = Cli::try_parse_from(["forge-guard", "audit", "--markdown", "--report"]).unwrap();
1439 if let Commands::Audit(args) = cli.command {
1440 assert!(args.shared.markdown);
1441 assert!(args.shared.report);
1442 } else {
1443 panic!("Expected Audit command");
1444 }
1445 }
1446
1447 #[test]
1448 fn test_quick_audit() {
1449 let cli = Cli::try_parse_from(["forge-guard", "audit", "--quick", "--summary"]).unwrap();
1450 if let Commands::Audit(args) = cli.command {
1451 assert!(args.quick);
1452 assert!(args.summary);
1453 } else {
1454 panic!("Expected Audit command");
1455 }
1456 }
1457
1458 #[test]
1459 fn test_all_chains_flag() {
1460 let cli = Cli::try_parse_from(["forge-guard", "audit", "--all-chains"]).unwrap();
1461 if let Commands::Audit(args) = cli.command {
1462 assert!(args.all_chains);
1463 assert_eq!(args.max_parallel_chains, 4);
1464 } else {
1465 panic!("Expected Audit command");
1466 }
1467 }
1468
1469 #[test]
1470 fn test_max_parallel_chains_default() {
1471 let cli = Cli::try_parse_from(["forge-guard", "audit"]).unwrap();
1472 if let Commands::Audit(args) = cli.command {
1473 assert_eq!(args.max_parallel_chains, 4);
1474 } else {
1475 panic!("Expected Audit command");
1476 }
1477 }
1478
1479 #[test]
1480 fn test_max_parallel_chains_custom() {
1481 let cli = Cli::try_parse_from([
1482 "forge-guard",
1483 "audit",
1484 "--all-chains",
1485 "--max-parallel-chains",
1486 "2",
1487 ])
1488 .unwrap();
1489 if let Commands::Audit(args) = cli.command {
1490 assert!(args.all_chains);
1491 assert_eq!(args.max_parallel_chains, 2);
1492 } else {
1493 panic!("Expected Audit command");
1494 }
1495 }
1496
1497 #[test]
1498 fn test_deploy_safe_args() {
1499 let cli =
1500 Cli::try_parse_from(["forge-guard", "deploy-safe", "SecureVault", "--verify"]).unwrap();
1501 if let Commands::DeploySafe(args) = cli.command {
1502 assert_eq!(args.contract.as_deref(), Some("SecureVault"));
1503 assert!(args.verify);
1504 } else {
1505 panic!("Expected DeploySafe command");
1506 }
1507 }
1508
1509 #[test]
1510 fn test_report_args_json() {
1511 let cli = Cli::try_parse_from(["forge-guard", "report", "--format", "json"]).unwrap();
1512 if let Commands::Report(args) = cli.command {
1513 assert_eq!(args.format, "json");
1514 assert!(!args.summary);
1515 } else {
1516 panic!("Expected Report command");
1517 }
1518 }
1519
1520 #[test]
1521 fn test_verify_all_without_address() {
1522 let cli = Cli::try_parse_from(["forge-guard", "verify", "--all"]).unwrap();
1523 if let Commands::Verify(args) = cli.command {
1524 assert!(args.all);
1525 assert!(args.address.is_none());
1526 } else {
1527 panic!("Expected Verify command");
1528 }
1529 }
1530
1531 #[test]
1532 fn test_report_summary() {
1533 let cli = Cli::try_parse_from(["forge-guard", "report", "--summary"]).unwrap();
1534 if let Commands::Report(args) = cli.command {
1535 assert!(args.summary);
1536 } else {
1537 panic!("Expected Report command");
1538 }
1539 }
1540
1541 #[test]
1542 fn test_scan_vulnerable_only() {
1543 let cli = Cli::try_parse_from(["forge-guard", "scan", "--vulnerable-only"]).unwrap();
1544 if let Commands::Scan(args) = cli.command {
1545 assert!(args.vulnerable_only);
1546 } else {
1547 panic!("Expected Scan command");
1548 }
1549 }
1550
1551 #[test]
1552 fn test_doctor_check_category() {
1553 let cli =
1554 Cli::try_parse_from(["forge-guard", "doctor", "--check", "dependencies"]).unwrap();
1555 if let Commands::Doctor(args) = cli.command {
1556 assert_eq!(args.check.as_deref(), Some("dependencies"));
1557 } else {
1558 panic!("Expected Doctor command");
1559 }
1560 }
1561
1562 #[test]
1563 fn test_gas_contract_specific() {
1564 let cli =
1565 Cli::try_parse_from(["forge-guard", "gas", "Vault", "--diff", "prev.json"]).unwrap();
1566 if let Commands::Gas(args) = cli.command {
1567 assert_eq!(args.contract.as_deref(), Some("Vault"));
1568 assert_eq!(args.diff.as_deref(), Some("prev.json"));
1569 } else {
1570 panic!("Expected Gas command");
1571 }
1572 }
1573
1574 #[test]
1575 fn test_verify_with_constructor_args() {
1576 let cli = Cli::try_parse_from([
1577 "forge-guard",
1578 "verify",
1579 "0xabc",
1580 "Token",
1581 "--constructor-args",
1582 "0x0001",
1583 ])
1584 .unwrap();
1585 if let Commands::Verify(args) = cli.command {
1586 assert_eq!(args.constructor_args.as_deref(), Some("0x0001"));
1587 } else {
1588 panic!("Expected Verify command");
1589 }
1590 }
1591
1592 #[test]
1593 fn test_upgrade_check_proxy() {
1594 let cli =
1595 Cli::try_parse_from(["forge-guard", "upgrade-check", "0xproxy", "0ximpl"]).unwrap();
1596 if let Commands::UpgradeCheck(args) = cli.command {
1597 assert_eq!(args.proxy.as_deref(), Some("0xproxy"));
1598 assert_eq!(args.implementation.as_deref(), Some("0ximpl"));
1599 } else {
1600 panic!("Expected UpgradeCheck command");
1601 }
1602 }
1603
1604 #[test]
1605 fn test_benchmark_save_and_compare() {
1606 let cli = Cli::try_parse_from([
1607 "forge-guard",
1608 "benchmark",
1609 "--save",
1610 "results.json",
1611 "--compare",
1612 "baseline.json",
1613 ])
1614 .unwrap();
1615 if let Commands::Benchmark(args) = cli.command {
1616 assert_eq!(
1617 args.save.as_deref(),
1618 Some(std::path::Path::new("results.json"))
1619 );
1620 assert_eq!(
1621 args.compare.as_deref(),
1622 Some(std::path::Path::new("baseline.json"))
1623 );
1624 } else {
1625 panic!("Expected Benchmark command");
1626 }
1627 }
1628
1629 #[test]
1630 fn test_watch_exclude() {
1631 let cli = Cli::try_parse_from(["forge-guard", "watch", "--exclude", "*.test.sol"]).unwrap();
1632 if let Commands::Watch(args) = cli.command {
1633 assert_eq!(args.exclude.as_deref(), Some("*.test.sol"));
1634 } else {
1635 panic!("Expected Watch command");
1636 }
1637 }
1638
1639 #[test]
1640 fn test_simulate_deployer() {
1641 let cli =
1642 Cli::try_parse_from(["forge-guard", "simulate", "--deployer", "0xdeployer"]).unwrap();
1643 if let Commands::Simulate(args) = cli.command {
1644 assert_eq!(args.deployer.as_deref(), Some("0xdeployer"));
1645 } else {
1646 panic!("Expected Simulate command");
1647 }
1648 }
1649
1650 #[test]
1651 fn test_security_disable_check() {
1652 let cli = Cli::try_parse_from(["forge-guard", "security", "disable", "FA-H-001"]).unwrap();
1653 if let Commands::Security(args) = cli.command {
1654 assert!(matches!(args.action, Some(SecurityAction::Disable { .. })));
1655 } else {
1656 panic!("Expected Security command");
1657 }
1658 }
1659
1660 #[test]
1661 fn test_from_env_try_parse() {
1662 let cli = Cli::try_parse_from(["forge-guard", "audit", "--report", "--json"]).unwrap();
1665 assert!(matches!(cli.command, Commands::Audit(_)));
1666 if let Commands::Audit(args) = cli.command {
1667 assert!(args.shared.report);
1668 assert!(args.shared.json);
1669 }
1670 }
1671
1672 #[test]
1673 fn test_cli_parse_import() {
1674 let cli = Cli::try_parse_from(["forge-guard", "import", "results.json"]).unwrap();
1675 assert!(matches!(cli.command, Commands::Import(_)));
1676 }
1677
1678 #[test]
1679 fn test_import_args_from_slither() {
1680 let cli = Cli::try_parse_from([
1681 "forge-guard",
1682 "import",
1683 "--from",
1684 "mythril",
1685 "mythril_out.json",
1686 "--findings",
1687 "reports/audit.json",
1688 ])
1689 .unwrap();
1690 if let Commands::Import(args) = cli.command {
1691 assert_eq!(args.from, "mythril");
1692 assert_eq!(
1693 args.input.as_deref(),
1694 Some(std::path::Path::new("mythril_out.json"))
1695 );
1696 assert_eq!(
1697 args.findings.as_deref(),
1698 Some(std::path::Path::new("reports/audit.json"))
1699 );
1700 } else {
1701 panic!("Expected Import command");
1702 }
1703 }
1704
1705 #[test]
1706 fn test_import_args_defaults() {
1707 let cli = Cli::try_parse_from(["forge-guard", "import"]).unwrap();
1708 if let Commands::Import(args) = cli.command {
1709 assert_eq!(args.from, "slither");
1710 assert!(args.input.is_none());
1711 assert!(args.findings.is_none());
1712 assert!(args.output.is_none());
1713 } else {
1714 panic!("Expected Import command");
1715 }
1716 }
1717
1718 #[test]
1719 fn test_cli_parse_notify() {
1720 let cli = Cli::try_parse_from(["forge-guard", "notify", "--dry-run"]).unwrap();
1721 assert!(matches!(cli.command, Commands::Notify(_)));
1722 }
1723
1724 #[test]
1725 fn test_notify_args() {
1726 let cli = Cli::try_parse_from([
1727 "forge-guard",
1728 "notify",
1729 "--webhook",
1730 "https://hooks.slack.com/services/T/B/X",
1731 "--findings",
1732 "reports/audit.json",
1733 "--on-critical",
1734 "--title",
1735 "Nightly audit",
1736 ])
1737 .unwrap();
1738 if let Commands::Notify(args) = cli.command {
1739 assert_eq!(
1740 args.webhook.as_deref(),
1741 Some("https://hooks.slack.com/services/T/B/X")
1742 );
1743 assert_eq!(
1744 args.findings.as_deref(),
1745 Some(std::path::Path::new("reports/audit.json"))
1746 );
1747 assert!(args.on_critical);
1748 assert!(!args.on_high);
1749 assert!(!args.dry_run);
1750 assert_eq!(args.title, "Nightly audit");
1751 } else {
1752 panic!("Expected Notify command");
1753 }
1754 }
1755
1756 #[test]
1757 fn test_notify_defaults() {
1758 let cli = Cli::try_parse_from(["forge-guard", "notify"]).unwrap();
1759 if let Commands::Notify(args) = cli.command {
1760 assert_eq!(args.title, "Forge Guard Audit");
1761 assert!(args.webhook.is_none());
1762 assert!(args.kind.is_none());
1763 assert!(args.severity.is_none());
1764 assert!(!args.dry_run);
1765 } else {
1766 panic!("Expected Notify command");
1767 }
1768 }
1769
1770 #[test]
1771 fn test_audit_suppression_flags() {
1772 let cli = Cli::try_parse_from([
1773 "forge-guard",
1774 "audit",
1775 "--suppressions",
1776 ".forge-guard-suppressions",
1777 "--show-suppressed",
1778 ])
1779 .unwrap();
1780 if let Commands::Audit(args) = cli.command {
1781 assert_eq!(
1782 args.suppressions.as_deref(),
1783 Some(std::path::Path::new(".forge-guard-suppressions"))
1784 );
1785 assert!(args.show_suppressed);
1786 assert!(!args.generate_suppressions);
1787 assert!(!args.notify);
1788 } else {
1789 panic!("Expected Audit command");
1790 }
1791 }
1792
1793 #[test]
1794 fn test_audit_generate_suppressions_and_notify() {
1795 let cli = Cli::try_parse_from([
1796 "forge-guard",
1797 "audit",
1798 "--generate-suppressions",
1799 "--notify",
1800 ])
1801 .unwrap();
1802 if let Commands::Audit(args) = cli.command {
1803 assert!(args.generate_suppressions);
1804 assert!(args.notify);
1805 assert!(args.suppressions.is_none());
1806 } else {
1807 panic!("Expected Audit command");
1808 }
1809 }
1810
1811 #[test]
1812 fn test_deploy_notify_flag() {
1813 let cli = Cli::try_parse_from(["forge-guard", "deploy", "--notify"]).unwrap();
1814 if let Commands::Deploy(args) = cli.command {
1815 assert!(args.notify);
1816 } else {
1817 panic!("Expected Deploy command");
1818 }
1819 }
1820
1821 #[test]
1822 fn test_deploy_safe_notify_flag() {
1823 let cli = Cli::try_parse_from(["forge-guard", "deploy-safe", "--notify"]).unwrap();
1824 if let Commands::DeploySafe(args) = cli.command {
1825 assert!(args.notify);
1826 } else {
1827 panic!("Expected DeploySafe command");
1828 }
1829 }
1830}