|
| 1 | +//! Clap parser snapshot tests for `ScanArgs`. |
| 2 | +//! |
| 3 | +//! These tests lock in the `scan` subcommand's CLI contract — every flag, |
| 4 | +//! short form, and default. Changes that flip a default or rename a flag |
| 5 | +//! must break these tests so the regression is caught before release. |
| 6 | +//! |
| 7 | +//! Two defaults are especially load-bearing and explicitly asserted: |
| 8 | +//! |
| 9 | +//! * `--batch-size` defaults to `100`. Downstream API batching assumes this. |
| 10 | +//! * `--download-mode` defaults to `"diff"`. This diverges from `repair`'s |
| 11 | +//! default and is a silent-regression risk if flipped. |
| 12 | +
|
| 13 | +use clap::Parser; |
| 14 | +use socket_patch_cli::commands::scan::ScanArgs; |
| 15 | +use socket_patch_cli::{Cli, Commands}; |
| 16 | + |
| 17 | +fn parse_scan(extra: &[&str]) -> ScanArgs { |
| 18 | + let mut argv = vec!["socket-patch", "scan"]; |
| 19 | + argv.extend_from_slice(extra); |
| 20 | + let cli = Cli::try_parse_from(&argv).expect("parse"); |
| 21 | + match cli.command { |
| 22 | + Commands::Scan(a) => a, |
| 23 | + _ => panic!("expected Scan"), |
| 24 | + } |
| 25 | +} |
| 26 | + |
| 27 | +fn try_parse_scan(extra: &[&str]) -> Result<ScanArgs, clap::Error> { |
| 28 | + let mut argv = vec!["socket-patch", "scan"]; |
| 29 | + argv.extend_from_slice(extra); |
| 30 | + let cli = Cli::try_parse_from(&argv)?; |
| 31 | + match cli.command { |
| 32 | + Commands::Scan(a) => Ok(a), |
| 33 | + _ => panic!("expected Scan"), |
| 34 | + } |
| 35 | +} |
| 36 | + |
| 37 | +#[test] |
| 38 | +fn defaults_match_contract() { |
| 39 | + let args = parse_scan(&[]); |
| 40 | + |
| 41 | + // Critical load-bearing defaults. |
| 42 | + assert_eq!(args.batch_size, 100, "--batch-size default is 100"); |
| 43 | + assert_eq!( |
| 44 | + args.download_mode, "diff", |
| 45 | + "--download-mode default is \"diff\"" |
| 46 | + ); |
| 47 | + |
| 48 | + // All other defaults from the scan table. |
| 49 | + assert_eq!(args.cwd, std::path::PathBuf::from(".")); |
| 50 | + assert_eq!(args.org, None); |
| 51 | + assert!(!args.json); |
| 52 | + assert!(!args.yes); |
| 53 | + assert!(!args.global); |
| 54 | + assert_eq!(args.global_prefix, None); |
| 55 | + assert_eq!(args.api_url, None); |
| 56 | + assert_eq!(args.api_token, None); |
| 57 | + assert_eq!(args.ecosystems, None); |
| 58 | +} |
| 59 | + |
| 60 | +#[test] |
| 61 | +fn yes_short_flag() { |
| 62 | + let args = parse_scan(&["-y"]); |
| 63 | + assert!(args.yes); |
| 64 | +} |
| 65 | + |
| 66 | +#[test] |
| 67 | +fn yes_long_flag() { |
| 68 | + let args = parse_scan(&["--yes"]); |
| 69 | + assert!(args.yes); |
| 70 | +} |
| 71 | + |
| 72 | +#[test] |
| 73 | +fn global_short_flag() { |
| 74 | + let args = parse_scan(&["-g"]); |
| 75 | + assert!(args.global); |
| 76 | +} |
| 77 | + |
| 78 | +#[test] |
| 79 | +fn global_long_flag() { |
| 80 | + let args = parse_scan(&["--global"]); |
| 81 | + assert!(args.global); |
| 82 | +} |
| 83 | + |
| 84 | +#[test] |
| 85 | +fn cwd_flag() { |
| 86 | + let args = parse_scan(&["--cwd", "/tmp/x"]); |
| 87 | + assert_eq!(args.cwd, std::path::PathBuf::from("/tmp/x")); |
| 88 | +} |
| 89 | + |
| 90 | +#[test] |
| 91 | +fn org_flag() { |
| 92 | + let args = parse_scan(&["--org", "myorg"]); |
| 93 | + assert_eq!(args.org.as_deref(), Some("myorg")); |
| 94 | +} |
| 95 | + |
| 96 | +#[test] |
| 97 | +fn json_flag() { |
| 98 | + let args = parse_scan(&["--json"]); |
| 99 | + assert!(args.json); |
| 100 | +} |
| 101 | + |
| 102 | +#[test] |
| 103 | +fn global_prefix_flag() { |
| 104 | + let args = parse_scan(&["--global-prefix", "/foo"]); |
| 105 | + assert_eq!(args.global_prefix, Some(std::path::PathBuf::from("/foo"))); |
| 106 | +} |
| 107 | + |
| 108 | +#[test] |
| 109 | +fn api_url_flag() { |
| 110 | + let args = parse_scan(&["--api-url", "https://api"]); |
| 111 | + assert_eq!(args.api_url.as_deref(), Some("https://api")); |
| 112 | +} |
| 113 | + |
| 114 | +#[test] |
| 115 | +fn api_token_flag() { |
| 116 | + let args = parse_scan(&["--api-token", "tok"]); |
| 117 | + assert_eq!(args.api_token.as_deref(), Some("tok")); |
| 118 | +} |
| 119 | + |
| 120 | +#[test] |
| 121 | +fn batch_size_500() { |
| 122 | + let args = parse_scan(&["--batch-size", "500"]); |
| 123 | + assert_eq!(args.batch_size, 500); |
| 124 | +} |
| 125 | + |
| 126 | +#[test] |
| 127 | +fn batch_size_1() { |
| 128 | + let args = parse_scan(&["--batch-size", "1"]); |
| 129 | + assert_eq!(args.batch_size, 1); |
| 130 | +} |
| 131 | + |
| 132 | +#[test] |
| 133 | +fn batch_size_0_parses() { |
| 134 | + // Clap accepts 0 as a valid usize. Whether 0 is a sensible batch size is |
| 135 | + // a command-level concern, not a parser concern. Lock in that the parser |
| 136 | + // itself does not reject it. |
| 137 | + let args = parse_scan(&["--batch-size", "0"]); |
| 138 | + assert_eq!(args.batch_size, 0); |
| 139 | +} |
| 140 | + |
| 141 | +#[test] |
| 142 | +fn batch_size_negative_fails() { |
| 143 | + // Use `--batch-size=-1` (rather than two separate tokens) so clap parses |
| 144 | + // `-1` as the value, not a stray short flag. The value must then fail |
| 145 | + // the usize conversion. |
| 146 | + let err = match try_parse_scan(&["--batch-size=-1"]) { |
| 147 | + Ok(_) => panic!("negative batch-size should fail to parse"), |
| 148 | + Err(e) => e, |
| 149 | + }; |
| 150 | + let kind = err.kind(); |
| 151 | + assert!( |
| 152 | + matches!( |
| 153 | + kind, |
| 154 | + clap::error::ErrorKind::ValueValidation | clap::error::ErrorKind::InvalidValue |
| 155 | + ), |
| 156 | + "expected ValueValidation or InvalidValue, got {:?}", |
| 157 | + kind |
| 158 | + ); |
| 159 | +} |
| 160 | + |
| 161 | +#[test] |
| 162 | +fn ecosystems_csv_multi() { |
| 163 | + let args = parse_scan(&["--ecosystems", "npm,pypi,cargo,maven"]); |
| 164 | + assert_eq!( |
| 165 | + args.ecosystems, |
| 166 | + Some(vec![ |
| 167 | + "npm".to_string(), |
| 168 | + "pypi".to_string(), |
| 169 | + "cargo".to_string(), |
| 170 | + "maven".to_string(), |
| 171 | + ]) |
| 172 | + ); |
| 173 | +} |
| 174 | + |
| 175 | +#[test] |
| 176 | +fn ecosystems_csv_single() { |
| 177 | + let args = parse_scan(&["--ecosystems", "npm"]); |
| 178 | + assert_eq!(args.ecosystems, Some(vec!["npm".to_string()])); |
| 179 | +} |
| 180 | + |
| 181 | +#[test] |
| 182 | +fn download_mode_diff() { |
| 183 | + let args = parse_scan(&["--download-mode", "diff"]); |
| 184 | + assert_eq!(args.download_mode, "diff"); |
| 185 | +} |
| 186 | + |
| 187 | +#[test] |
| 188 | +fn download_mode_package() { |
| 189 | + let args = parse_scan(&["--download-mode", "package"]); |
| 190 | + assert_eq!(args.download_mode, "package"); |
| 191 | +} |
| 192 | + |
| 193 | +#[test] |
| 194 | +fn download_mode_file() { |
| 195 | + let args = parse_scan(&["--download-mode", "file"]); |
| 196 | + assert_eq!(args.download_mode, "file"); |
| 197 | +} |
| 198 | + |
| 199 | +#[test] |
| 200 | +fn unknown_flag_fails() { |
| 201 | + let err = match try_parse_scan(&["--not-a-real-flag"]) { |
| 202 | + Ok(_) => panic!("unknown flag should fail to parse"), |
| 203 | + Err(e) => e, |
| 204 | + }; |
| 205 | + assert_eq!(err.kind(), clap::error::ErrorKind::UnknownArgument); |
| 206 | +} |
0 commit comments