xref: /aosp_15_r20/tools/netsim/rust/cli/src/requests.rs (revision cf78ab8cffb8fc9207af348f23af247fb04370a6)
1 // Copyright 2022 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 use crate::args::{self, Beacon, Command};
16 use crate::ffi::frontend_client_ffi::GrpcMethod;
17 
18 impl args::Command {
19     /// Return the respective GrpcMethod for the command
grpc_method(&self) -> GrpcMethod20     pub fn grpc_method(&self) -> GrpcMethod {
21         match self {
22             Command::Version => GrpcMethod::GetVersion,
23             Command::Radio(_) => GrpcMethod::PatchDevice,
24             Command::Move(_) => GrpcMethod::PatchDevice,
25             Command::Devices(_) => GrpcMethod::ListDevice,
26             Command::Reset => GrpcMethod::Reset,
27             Command::Capture(cmd) => match cmd {
28                 args::Capture::List(_) => GrpcMethod::ListCapture,
29                 args::Capture::Get(_) => GrpcMethod::GetCapture,
30                 args::Capture::Patch(_) => GrpcMethod::PatchCapture,
31             },
32             Command::Gui => {
33                 panic!("No GrpcMethod for Ui Command.");
34             }
35             Command::Artifact => {
36                 panic!("No GrpcMethod for Artifact Command.");
37             }
38             Command::Beacon(action) => match action {
39                 Beacon::Create(_) => GrpcMethod::CreateDevice,
40                 Beacon::Patch(_) => GrpcMethod::PatchDevice,
41                 Beacon::Remove(_) => GrpcMethod::DeleteChip,
42             },
43             Command::Bumble => {
44                 panic!("No GrpcMethod for Bumble Command.");
45             }
46         }
47     }
48 }
49 
50 #[cfg(test)]
51 mod tests {
52     use super::*;
53     use args::{BinaryProtobuf, NetsimArgs};
54     use clap::Parser;
55     use netsim_proto::frontend::{
56         patch_device_request::PatchDeviceFields as PatchDeviceFieldsProto, CreateDeviceRequest,
57         PatchDeviceRequest,
58     };
59     use netsim_proto::model::chip::ble_beacon::AdvertiseData as AdvertiseDataProto;
60     use netsim_proto::model::chip::{
61         ble_beacon::{
62             advertise_settings::{
63                 AdvertiseMode as AdvertiseModeProto, AdvertiseTxPower as AdvertiseTxPowerProto,
64                 Interval as IntervalProto, Tx_power as TxPowerProto,
65             },
66             AdvertiseSettings as AdvertiseSettingsProto,
67         },
68         BleBeacon as BleBeaconProto, Chip as ChipKindProto,
69     };
70     use netsim_proto::model::chip_create::{
71         BleBeaconCreate as BleBeaconCreateProto, Chip as ChipKindCreateProto,
72     };
73     use netsim_proto::model::{
74         Chip as ChipProto, ChipCreate as ChipCreateProto, DeviceCreate as DeviceCreateProto,
75     };
76     use netsim_proto::{
77         common::ChipKind,
78         frontend,
79         model::{
80             self,
81             chip::{Bluetooth as Chip_Bluetooth, Radio as Chip_Radio},
82             Position,
83         },
84     };
85     use protobuf::Message;
86     use protobuf::MessageField;
87 
test_command( command: &str, expected_grpc_method: GrpcMethod, expected_request_byte_str: BinaryProtobuf, )88     fn test_command(
89         command: &str,
90         expected_grpc_method: GrpcMethod,
91         expected_request_byte_str: BinaryProtobuf,
92     ) {
93         let command = NetsimArgs::parse_from(command.split_whitespace()).command;
94         assert_eq!(expected_grpc_method, command.grpc_method());
95         let request = command.get_request_bytes();
96         assert_eq!(request, expected_request_byte_str);
97     }
98 
99     #[test]
test_version_request()100     fn test_version_request() {
101         test_command("netsim-cli version", GrpcMethod::GetVersion, Vec::new())
102     }
103 
get_expected_radio(name: &str, radio_type: &str, state: &str) -> BinaryProtobuf104     fn get_expected_radio(name: &str, radio_type: &str, state: &str) -> BinaryProtobuf {
105         let mut chip = model::Chip { ..Default::default() };
106         let chip_state = state == "up";
107         if radio_type == "wifi" {
108             let mut wifi_chip = Chip_Radio::new();
109             wifi_chip.state = chip_state.into();
110             chip.set_wifi(wifi_chip);
111             chip.kind = ChipKind::WIFI.into();
112         } else if radio_type == "uwb" {
113             let mut uwb_chip = Chip_Radio::new();
114             uwb_chip.state = chip_state.into();
115             chip.set_uwb(uwb_chip);
116             chip.kind = ChipKind::UWB.into();
117         } else {
118             let mut bt_chip = Chip_Bluetooth::new();
119             let mut bt_chip_radio = Chip_Radio::new();
120             bt_chip_radio.state = chip_state.into();
121             if radio_type == "ble" {
122                 bt_chip.low_energy = Some(bt_chip_radio).into();
123             } else {
124                 bt_chip.classic = Some(bt_chip_radio).into();
125             }
126             chip.kind = ChipKind::BLUETOOTH.into();
127             chip.set_bt(bt_chip);
128         }
129         let mut result = frontend::PatchDeviceRequest::new();
130         let mut device = PatchDeviceFieldsProto::new();
131         device.name = Some(name.to_string());
132         device.chips.push(chip);
133         result.device = Some(device).into();
134         result.write_to_bytes().unwrap()
135     }
136 
137     #[test]
test_radio_ble()138     fn test_radio_ble() {
139         test_command(
140             "netsim-cli radio ble down 1000",
141             GrpcMethod::PatchDevice,
142             get_expected_radio("1000", "ble", "down"),
143         );
144         test_command(
145             "netsim-cli radio ble up 1000",
146             GrpcMethod::PatchDevice,
147             get_expected_radio("1000", "ble", "up"),
148         );
149     }
150 
151     #[test]
test_radio_ble_aliases()152     fn test_radio_ble_aliases() {
153         test_command(
154             "netsim-cli radio ble Down 1000",
155             GrpcMethod::PatchDevice,
156             get_expected_radio("1000", "ble", "down"),
157         );
158         test_command(
159             "netsim-cli radio ble Up 1000",
160             GrpcMethod::PatchDevice,
161             get_expected_radio("1000", "ble", "up"),
162         );
163         test_command(
164             "netsim-cli radio ble DOWN 1000",
165             GrpcMethod::PatchDevice,
166             get_expected_radio("1000", "ble", "down"),
167         );
168         test_command(
169             "netsim-cli radio ble UP 1000",
170             GrpcMethod::PatchDevice,
171             get_expected_radio("1000", "ble", "up"),
172         );
173     }
174 
175     #[test]
test_radio_classic()176     fn test_radio_classic() {
177         test_command(
178             "netsim-cli radio classic down 100",
179             GrpcMethod::PatchDevice,
180             get_expected_radio("100", "classic", "down"),
181         );
182         test_command(
183             "netsim-cli radio classic up 100",
184             GrpcMethod::PatchDevice,
185             get_expected_radio("100", "classic", "up"),
186         );
187     }
188 
189     #[test]
test_radio_wifi()190     fn test_radio_wifi() {
191         test_command(
192             "netsim-cli radio wifi down a",
193             GrpcMethod::PatchDevice,
194             get_expected_radio("a", "wifi", "down"),
195         );
196         test_command(
197             "netsim-cli radio wifi up b",
198             GrpcMethod::PatchDevice,
199             get_expected_radio("b", "wifi", "up"),
200         );
201     }
202 
203     #[test]
test_radio_uwb()204     fn test_radio_uwb() {
205         test_command(
206             "netsim-cli radio uwb down a",
207             GrpcMethod::PatchDevice,
208             get_expected_radio("a", "uwb", "down"),
209         );
210         test_command(
211             "netsim-cli radio uwb up b",
212             GrpcMethod::PatchDevice,
213             get_expected_radio("b", "uwb", "up"),
214         );
215     }
216 
get_expected_move(name: &str, x: f32, y: f32, z: Option<f32>) -> BinaryProtobuf217     fn get_expected_move(name: &str, x: f32, y: f32, z: Option<f32>) -> BinaryProtobuf {
218         let mut result = frontend::PatchDeviceRequest::new();
219         let mut device = PatchDeviceFieldsProto::new();
220         let position = Position { x, y, z: z.unwrap_or_default(), ..Default::default() };
221         device.name = Some(name.to_string());
222         device.position = Some(position).into();
223         result.device = Some(device).into();
224         result.write_to_bytes().unwrap()
225     }
226 
227     #[test]
test_move_int()228     fn test_move_int() {
229         test_command(
230             "netsim-cli move 1 1 2 3",
231             GrpcMethod::PatchDevice,
232             get_expected_move("1", 1.0, 2.0, Some(3.0)),
233         )
234     }
235 
236     #[test]
test_move_float()237     fn test_move_float() {
238         test_command(
239             "netsim-cli move 1000 1.2 3.4 5.6",
240             GrpcMethod::PatchDevice,
241             get_expected_move("1000", 1.2, 3.4, Some(5.6)),
242         )
243     }
244 
245     #[test]
test_move_mixed()246     fn test_move_mixed() {
247         test_command(
248             "netsim-cli move 1000 1.1 2 3.4",
249             GrpcMethod::PatchDevice,
250             get_expected_move("1000", 1.1, 2.0, Some(3.4)),
251         )
252     }
253 
254     #[test]
test_move_no_z()255     fn test_move_no_z() {
256         test_command(
257             "netsim-cli move 1000 1.2 3.4",
258             GrpcMethod::PatchDevice,
259             get_expected_move("1000", 1.2, 3.4, None),
260         )
261     }
262 
263     #[test]
test_devices()264     fn test_devices() {
265         test_command("netsim-cli devices", GrpcMethod::ListDevice, Vec::new())
266     }
267 
268     #[test]
test_reset()269     fn test_reset() {
270         test_command("netsim-cli reset", GrpcMethod::Reset, Vec::new())
271     }
272 
273     #[test]
test_capture_list()274     fn test_capture_list() {
275         test_command("netsim-cli capture list", GrpcMethod::ListCapture, Vec::new())
276     }
277 
278     #[test]
test_capture_list_alias()279     fn test_capture_list_alias() {
280         test_command("netsim-cli pcap list", GrpcMethod::ListCapture, Vec::new())
281     }
282 
283     //TODO: Add capture patch and get tests once able to run tests with cxx definitions
284 
get_create_device_req_bytes( device_name: &str, chip_name: &str, settings: AdvertiseSettingsProto, adv_data: AdvertiseDataProto, scan_response: AdvertiseDataProto, ) -> Vec<u8>285     fn get_create_device_req_bytes(
286         device_name: &str,
287         chip_name: &str,
288         settings: AdvertiseSettingsProto,
289         adv_data: AdvertiseDataProto,
290         scan_response: AdvertiseDataProto,
291     ) -> Vec<u8> {
292         let device = MessageField::some(DeviceCreateProto {
293             name: String::from(device_name),
294             chips: vec![ChipCreateProto {
295                 name: String::from(chip_name),
296                 kind: ChipKind::BLUETOOTH_BEACON.into(),
297                 chip: Some(ChipKindCreateProto::BleBeacon(BleBeaconCreateProto {
298                     settings: MessageField::some(settings),
299                     adv_data: MessageField::some(adv_data),
300                     scan_response: MessageField::some(scan_response),
301                     ..Default::default()
302                 })),
303                 ..Default::default()
304             }],
305             ..Default::default()
306         });
307 
308         CreateDeviceRequest { device, ..Default::default() }.write_to_bytes().unwrap()
309     }
310 
get_patch_device_req_bytes( device_name: &str, chip_name: &str, settings: AdvertiseSettingsProto, adv_data: AdvertiseDataProto, scan_response: AdvertiseDataProto, ) -> Vec<u8>311     fn get_patch_device_req_bytes(
312         device_name: &str,
313         chip_name: &str,
314         settings: AdvertiseSettingsProto,
315         adv_data: AdvertiseDataProto,
316         scan_response: AdvertiseDataProto,
317     ) -> Vec<u8> {
318         let device = MessageField::some(PatchDeviceFieldsProto {
319             name: Some(String::from(device_name)),
320             chips: vec![ChipProto {
321                 name: String::from(chip_name),
322                 kind: ChipKind::BLUETOOTH_BEACON.into(),
323                 chip: Some(ChipKindProto::BleBeacon(BleBeaconProto {
324                     bt: MessageField::some(Chip_Bluetooth::new()),
325                     settings: MessageField::some(settings),
326                     adv_data: MessageField::some(adv_data),
327                     scan_response: MessageField::some(scan_response),
328                     ..Default::default()
329                 })),
330                 ..Default::default()
331             }],
332             ..Default::default()
333         });
334 
335         PatchDeviceRequest { device, ..Default::default() }.write_to_bytes().unwrap()
336     }
337 
338     #[test]
test_beacon_create_all_params_ble()339     fn test_beacon_create_all_params_ble() {
340         let device_name = String::from("device");
341         let chip_name = String::from("chip");
342 
343         let timeout = 1234;
344         let manufacturer_data = vec![0x12, 0x34];
345 
346         let settings = AdvertiseSettingsProto {
347             interval: Some(IntervalProto::AdvertiseMode(AdvertiseModeProto::BALANCED.into())),
348             tx_power: Some(TxPowerProto::TxPowerLevel(AdvertiseTxPowerProto::ULTRA_LOW.into())),
349             scannable: true,
350             timeout,
351             ..Default::default()
352         };
353 
354         let adv_data = AdvertiseDataProto {
355             include_device_name: true,
356             include_tx_power_level: true,
357             manufacturer_data,
358             ..Default::default()
359         };
360 
361         let request = get_create_device_req_bytes(
362             &device_name,
363             &chip_name,
364             settings,
365             adv_data,
366             Default::default(),
367         );
368 
369         test_command(
370             format!(
371                 "netsim-cli beacon create ble {} {} --advertise-mode balanced --tx-power-level ultra-low --scannable --timeout {} --include-device-name --include-tx-power-level --manufacturer-data 0x1234",
372                 device_name, chip_name, timeout,
373             )
374             .as_str(),
375             GrpcMethod::CreateDevice,
376             request,
377         )
378     }
379 
380     #[test]
test_beacon_patch_all_params_ble()381     fn test_beacon_patch_all_params_ble() {
382         let device_name = String::from("device");
383         let chip_name = String::from("chip");
384 
385         let interval = 1234;
386         let timeout = 9999;
387         let tx_power_level = -3;
388         let manufacturer_data = vec![0xab, 0xcd, 0xef];
389 
390         let settings = AdvertiseSettingsProto {
391             interval: Some(IntervalProto::Milliseconds(interval)),
392             tx_power: Some(TxPowerProto::Dbm(tx_power_level)),
393             scannable: true,
394             timeout,
395             ..Default::default()
396         };
397         let adv_data = AdvertiseDataProto {
398             include_device_name: true,
399             include_tx_power_level: true,
400             manufacturer_data,
401             ..Default::default()
402         };
403 
404         let request = get_patch_device_req_bytes(
405             &device_name,
406             &chip_name,
407             settings,
408             adv_data,
409             Default::default(),
410         );
411 
412         test_command(
413             format!(
414                 "netsim-cli beacon patch ble {} {} --advertise-mode {} --scannable --timeout {} --tx-power-level {} --manufacturer-data 0xabcdef --include-device-name --include-tx-power-level",
415                 device_name, chip_name, interval, timeout, tx_power_level
416             )
417             .as_str(),
418             GrpcMethod::PatchDevice,
419             request,
420         )
421     }
422 
423     #[test]
test_beacon_create_scan_response()424     fn test_beacon_create_scan_response() {
425         let device_name = String::from("device");
426         let chip_name = String::from("chip");
427 
428         let scan_response = AdvertiseDataProto {
429             include_device_name: true,
430             include_tx_power_level: true,
431             manufacturer_data: vec![0x21, 0xbe, 0xef],
432             ..Default::default()
433         };
434 
435         let request = get_create_device_req_bytes(
436             &device_name,
437             &chip_name,
438             Default::default(),
439             Default::default(),
440             scan_response,
441         );
442 
443         test_command(
444             format!(
445                 "netsim-cli beacon create ble {} {} --scan-response-include-device-name --scan-response-include-tx-power-level --scan-response-manufacturer-data 0x21beef",
446                 device_name, chip_name
447             )
448             .as_str(),
449             GrpcMethod::CreateDevice,
450             request,
451         );
452     }
453 
454     #[test]
test_beacon_patch_scan_response()455     fn test_beacon_patch_scan_response() {
456         let device_name = String::from("device");
457         let chip_name = String::from("chip");
458 
459         let scan_response = AdvertiseDataProto {
460             include_device_name: true,
461             include_tx_power_level: true,
462             manufacturer_data: vec![0x59, 0xbe, 0xac, 0x09],
463             ..Default::default()
464         };
465 
466         let request = get_patch_device_req_bytes(
467             &device_name,
468             &chip_name,
469             Default::default(),
470             Default::default(),
471             scan_response,
472         );
473 
474         test_command(
475             format!(
476                 "netsim-cli beacon patch ble {} {} --scan-response-include-device-name --scan-response-include-tx-power-level --scan-response-manufacturer-data 59beac09",
477                 device_name, chip_name
478             )
479             .as_str(),
480             GrpcMethod::PatchDevice,
481             request,
482         );
483     }
484 
485     #[test]
test_beacon_create_ble_tx_power()486     fn test_beacon_create_ble_tx_power() {
487         let device_name = String::from("device");
488         let chip_name = String::from("chip");
489 
490         let settings = AdvertiseSettingsProto {
491             tx_power: Some(TxPowerProto::TxPowerLevel(AdvertiseTxPowerProto::HIGH.into())),
492             ..Default::default()
493         };
494         let adv_data = AdvertiseDataProto { include_tx_power_level: true, ..Default::default() };
495 
496         let request = get_create_device_req_bytes(
497             &device_name,
498             &chip_name,
499             settings,
500             adv_data,
501             Default::default(),
502         );
503 
504         test_command(
505             format!(
506                 "netsim-cli beacon create ble {} {} --tx-power-level high --include-tx-power-level",
507                 device_name, chip_name
508             )
509             .as_str(),
510             GrpcMethod::CreateDevice,
511             request,
512         )
513     }
514 
515     #[test]
test_beacon_create_default()516     fn test_beacon_create_default() {
517         let request = get_create_device_req_bytes(
518             Default::default(),
519             Default::default(),
520             Default::default(),
521             Default::default(),
522             Default::default(),
523         );
524 
525         test_command("netsim-cli beacon create ble", GrpcMethod::CreateDevice, request)
526     }
527 
528     #[test]
test_beacon_patch_interval()529     fn test_beacon_patch_interval() {
530         let device_name = String::from("device");
531         let chip_name = String::from("chip");
532 
533         let settings = AdvertiseSettingsProto {
534             interval: Some(IntervalProto::AdvertiseMode(AdvertiseModeProto::LOW_LATENCY.into())),
535             ..Default::default()
536         };
537 
538         let request = get_patch_device_req_bytes(
539             &device_name,
540             &chip_name,
541             settings,
542             Default::default(),
543             Default::default(),
544         );
545 
546         test_command(
547             format!(
548                 "netsim-cli beacon patch ble {} {} --advertise-mode low-latency",
549                 device_name, chip_name
550             )
551             .as_str(),
552             GrpcMethod::PatchDevice,
553             request,
554         )
555     }
556 
557     #[test]
test_beacon_create_ble_with_address()558     fn test_beacon_create_ble_with_address() {
559         let address = String::from("12:34:56:78:9a:bc");
560 
561         let device = MessageField::some(DeviceCreateProto {
562             chips: vec![ChipCreateProto {
563                 kind: ChipKind::BLUETOOTH_BEACON.into(),
564                 chip: Some(ChipKindCreateProto::BleBeacon(BleBeaconCreateProto {
565                     address: address.clone(),
566                     settings: MessageField::some(AdvertiseSettingsProto::default()),
567                     adv_data: MessageField::some(AdvertiseDataProto::default()),
568                     scan_response: MessageField::some(AdvertiseDataProto::default()),
569                     ..Default::default()
570                 })),
571                 ..Default::default()
572             }],
573             ..Default::default()
574         });
575 
576         let request = frontend::CreateDeviceRequest { device, ..Default::default() }
577             .write_to_bytes()
578             .unwrap();
579 
580         test_command(
581             format!("netsim-cli beacon create ble --address {}", address).as_str(),
582             GrpcMethod::CreateDevice,
583             request,
584         )
585     }
586 
587     #[test]
test_beacon_patch_ble_with_address()588     fn test_beacon_patch_ble_with_address() {
589         let address = String::from("12:34:56:78:9a:bc");
590         let device_name = String::from("device");
591         let chip_name = String::from("chip");
592 
593         let device = MessageField::some(PatchDeviceFieldsProto {
594             name: Some(device_name.clone()),
595             chips: vec![ChipProto {
596                 name: chip_name.clone(),
597                 kind: ChipKind::BLUETOOTH_BEACON.into(),
598                 chip: Some(ChipKindProto::BleBeacon(BleBeaconProto {
599                     bt: MessageField::some(Chip_Bluetooth::new()),
600                     address: address.clone(),
601                     settings: MessageField::some(AdvertiseSettingsProto::default()),
602                     adv_data: MessageField::some(AdvertiseDataProto::default()),
603                     scan_response: MessageField::some(AdvertiseDataProto::default()),
604                     ..Default::default()
605                 })),
606                 ..Default::default()
607             }],
608             ..Default::default()
609         });
610 
611         let request =
612             frontend::PatchDeviceRequest { device, ..Default::default() }.write_to_bytes().unwrap();
613 
614         test_command(
615             format!(
616                 "netsim-cli beacon patch ble {} {} --address {}",
617                 device_name, chip_name, address
618             )
619             .as_str(),
620             GrpcMethod::PatchDevice,
621             request,
622         )
623     }
624 
625     #[test]
test_beacon_negative_timeout_fails()626     fn test_beacon_negative_timeout_fails() {
627         let command = String::from("netsim-cli beacon create ble --timeout -1234");
628         assert!(NetsimArgs::try_parse_from(command.split_whitespace()).is_err());
629     }
630 
631     #[test]
test_create_beacon_large_tx_power_fails()632     fn test_create_beacon_large_tx_power_fails() {
633         let command =
634             format!("netsim-cli beacon create ble --tx-power-level {}", (i8::MAX as i32) + 1);
635         assert!(NetsimArgs::try_parse_from(command.split_whitespace()).is_err());
636     }
637 
638     #[test]
test_create_beacon_unknown_mode_fails()639     fn test_create_beacon_unknown_mode_fails() {
640         let command = String::from("netsim-cli beacon create ble --advertise-mode not-a-mode");
641         assert!(NetsimArgs::try_parse_from(command.split_whitespace()).is_err());
642     }
643 
644     #[test]
test_patch_beacon_negative_timeout_fails()645     fn test_patch_beacon_negative_timeout_fails() {
646         let command = String::from("netsim-cli beacon patch ble --timeout -1234");
647         assert!(NetsimArgs::try_parse_from(command.split_whitespace()).is_err());
648     }
649 
650     #[test]
test_patch_beacon_large_tx_power_fails()651     fn test_patch_beacon_large_tx_power_fails() {
652         let command =
653             format!("netsim-cli beacon patch ble --tx-power-level {}", (i8::MAX as i32) + 1);
654         assert!(NetsimArgs::try_parse_from(command.split_whitespace()).is_err());
655     }
656 
657     #[test]
test_patch_beacon_unknown_mode_fails()658     fn test_patch_beacon_unknown_mode_fails() {
659         let command = String::from("netsim-cli beacon patch ble --advertise-mode not-a-mode");
660         assert!(NetsimArgs::try_parse_from(command.split_whitespace()).is_err());
661     }
662 
663     #[test]
test_create_beacon_mfg_data_fails()664     fn test_create_beacon_mfg_data_fails() {
665         let command = String::from("netsim-cli beacon create ble --manufacturer-data not-a-number");
666         assert!(NetsimArgs::try_parse_from(command.split_whitespace()).is_err());
667     }
668 
669     #[test]
test_patch_beacon_mfg_data_fails()670     fn test_patch_beacon_mfg_data_fails() {
671         let command = String::from("netsim-cli beacon patch ble --manufacturer-data not-a-number");
672         assert!(NetsimArgs::try_parse_from(command.split_whitespace()).is_err());
673     }
674 }
675