Skip to content

Commit 8ece1bf

Browse files
committed
test(cli): pin ScanArgs flag surface (batch-size=100, download-mode=diff)
Adds clap parser snapshot tests for the `scan` subcommand to lock in every flag, short form, and default. The two load-bearing defaults (`--batch-size = 100`, `--download-mode = "diff"`) are explicitly asserted to catch silent regressions. Covers CSV splitting of `--ecosystems`, all three valid `--download-mode` values, and the expected clap error kinds for negative batch sizes and unknown flags. Assisted-by: Claude Code:claude-opus-4-7
1 parent 0bd4f50 commit 8ece1bf

1 file changed

Lines changed: 206 additions & 0 deletions

File tree

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
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

Comments
 (0)