@@ -94,3 +94,162 @@ pub fn parse_with_uuid_fallback(argv: Vec<String>) -> Result<Cli, clap::Error> {
9494 }
9595 }
9696}
97+
98+ #[ cfg( test) ]
99+ mod tests {
100+ //! Unit tests for the bare-UUID fallback. These tests lock in the
101+ //! `socket-patch <UUID>` rewrite shortcut and the shape predicate it
102+ //! uses — both of which are part of the CLI contract (see
103+ //! `CLI_CONTRACT.md`).
104+ use super :: * ;
105+
106+ // ---------- looks_like_uuid ----------
107+
108+ #[ test]
109+ fn looks_like_uuid_accepts_canonical_lowercase ( ) {
110+ assert ! ( looks_like_uuid( "80630680-4da6-45f9-bba8-b888e0ffd58c" ) ) ;
111+ }
112+
113+ #[ test]
114+ fn looks_like_uuid_accepts_uppercase ( ) {
115+ // `is_ascii_hexdigit` accepts A-F as well as a-f, so all-uppercase
116+ // UUIDs must still pass the shape check.
117+ assert ! ( looks_like_uuid( "80630680-4DA6-45F9-BBA8-B888E0FFD58C" ) ) ;
118+ }
119+
120+ #[ test]
121+ fn looks_like_uuid_accepts_mixed_case ( ) {
122+ assert ! ( looks_like_uuid( "80630680-4Da6-45F9-bBa8-B888e0FfD58c" ) ) ;
123+ }
124+
125+ #[ test]
126+ fn looks_like_uuid_rejects_four_groups ( ) {
127+ // 8-4-4-4 — missing the final 12-char group.
128+ assert ! ( !looks_like_uuid( "80630680-4da6-45f9-bba8" ) ) ;
129+ }
130+
131+ #[ test]
132+ fn looks_like_uuid_rejects_six_groups ( ) {
133+ // One too many groups — the split count must be exactly 5.
134+ assert ! ( !looks_like_uuid(
135+ "80630680-4da6-45f9-bba8-b888e0ffd58c-extra"
136+ ) ) ;
137+ }
138+
139+ #[ test]
140+ fn looks_like_uuid_rejects_8_4_4_4_13_group_lengths ( ) {
141+ // Final group has 13 chars instead of 12.
142+ assert ! ( !looks_like_uuid( "80630680-4da6-45f9-bba8-b888e0ffd58cc" ) ) ;
143+ }
144+
145+ #[ test]
146+ fn looks_like_uuid_rejects_7_4_4_4_12_group_lengths ( ) {
147+ // First group has 7 chars instead of 8.
148+ assert ! ( !looks_like_uuid( "8063068-4da6-45f9-bba8-b888e0ffd58c0" ) ) ;
149+ }
150+
151+ #[ test]
152+ fn looks_like_uuid_rejects_non_hex_chars ( ) {
153+ // `g` is not a hex digit — must fail even though the shape is right.
154+ assert ! ( !looks_like_uuid( "g0630680-4da6-45f9-bba8-b888e0ffd58c" ) ) ;
155+ assert ! ( !looks_like_uuid( "80630680-4dz6-45f9-bba8-b888e0ffd58c" ) ) ;
156+ assert ! ( !looks_like_uuid( "80630680-4da6-45f9-bba8-b888e0ffd58z" ) ) ;
157+ }
158+
159+ #[ test]
160+ fn looks_like_uuid_rejects_empty_string ( ) {
161+ assert ! ( !looks_like_uuid( "" ) ) ;
162+ }
163+
164+ #[ test]
165+ fn looks_like_uuid_rejects_string_with_no_dashes ( ) {
166+ // 32 hex chars, no dashes — close to a UUID but not the right shape.
167+ assert ! ( !looks_like_uuid( "806306804da645f9bba8b888e0ffd58c" ) ) ;
168+ }
169+
170+ #[ test]
171+ fn looks_like_uuid_rejects_bare_dashes ( ) {
172+ // Five empty groups — split count is right, group lengths aren't.
173+ assert ! ( !looks_like_uuid( "----" ) ) ;
174+ }
175+
176+ // ---------- parse_with_uuid_fallback ----------
177+
178+ const UUID : & str = "80630680-4da6-45f9-bba8-b888e0ffd58c" ;
179+
180+ fn argv ( items : & [ & str ] ) -> Vec < String > {
181+ items. iter ( ) . map ( |s| ( * s) . to_string ( ) ) . collect ( )
182+ }
183+
184+ #[ test]
185+ fn fallback_rewrites_bare_uuid_to_get ( ) {
186+ let cli = parse_with_uuid_fallback ( argv ( & [ "socket-patch" , UUID ] ) ) . unwrap ( ) ;
187+ match cli. command {
188+ Commands :: Get ( args) => assert_eq ! ( args. identifier, UUID ) ,
189+ _ => panic ! ( "expected Commands::Get" ) ,
190+ }
191+ }
192+
193+ #[ test]
194+ fn fallback_preserves_trailing_flags ( ) {
195+ // Flags after the UUID must be forwarded to the synthesized `get`.
196+ let cli = parse_with_uuid_fallback ( argv ( & [ "socket-patch" , UUID , "--json" ] ) ) . unwrap ( ) ;
197+ match cli. command {
198+ Commands :: Get ( args) => {
199+ assert_eq ! ( args. identifier, UUID ) ;
200+ assert ! ( args. json, "--json should be forwarded to get" ) ;
201+ }
202+ _ => panic ! ( "expected Commands::Get" ) ,
203+ }
204+ }
205+
206+ #[ test]
207+ fn fallback_returns_original_error_when_first_arg_is_not_uuid ( ) {
208+ // No rewrite should happen; the original clap error must surface.
209+ // `Cli` doesn't derive `Debug`, so `unwrap_err()` doesn't compile —
210+ // pull the error out via `match` instead.
211+ let err = match parse_with_uuid_fallback ( argv ( & [ "socket-patch" , "not-a-uuid" ] ) ) {
212+ Ok ( _) => panic ! ( "expected parse to fail" ) ,
213+ Err ( e) => e,
214+ } ;
215+ assert_eq ! ( err. kind( ) , clap:: error:: ErrorKind :: InvalidSubcommand ) ;
216+ }
217+
218+ #[ test]
219+ fn fallback_is_skipped_when_normal_parse_succeeds ( ) {
220+ // `list` parses normally — fallback should not engage.
221+ let cli = parse_with_uuid_fallback ( argv ( & [ "socket-patch" , "list" ] ) ) . unwrap ( ) ;
222+ assert ! ( matches!( cli. command, Commands :: List ( _) ) ) ;
223+ }
224+
225+ #[ test]
226+ fn fallback_does_not_double_rewrite_explicit_get ( ) {
227+ // `socket-patch get <UUID>` already parses; fallback never runs.
228+ let cli = parse_with_uuid_fallback ( argv ( & [ "socket-patch" , "get" , UUID ] ) ) . unwrap ( ) ;
229+ match cli. command {
230+ Commands :: Get ( args) => assert_eq ! ( args. identifier, UUID ) ,
231+ _ => panic ! ( "expected Commands::Get" ) ,
232+ }
233+ }
234+
235+ #[ test]
236+ fn fallback_surfaces_original_error_when_rewrite_also_fails ( ) {
237+ // UUID is valid-shaped so a rewrite is attempted, but `get` doesn't
238+ // accept this flag — the rewrite parse fails and we must return the
239+ // ORIGINAL error (the one from the un-rewritten parse), not the
240+ // rewrite's error.
241+ let err = match parse_with_uuid_fallback ( argv ( & [
242+ "socket-patch" ,
243+ UUID ,
244+ "--invalid-flag-that-get-does-not-accept" ,
245+ ] ) ) {
246+ Ok ( _) => panic ! ( "expected parse to fail" ) ,
247+ Err ( e) => e,
248+ } ;
249+ // The original parse failed because `<UUID>` isn't a known
250+ // subcommand, so the surfaced error must be InvalidSubcommand —
251+ // NOT UnknownArgument (which is what the rewrite parse would have
252+ // produced).
253+ assert_eq ! ( err. kind( ) , clap:: error:: ErrorKind :: InvalidSubcommand ) ;
254+ }
255+ }
0 commit comments