Skip to content

Commit 5a85ba3

Browse files
qdotclaude
andcommitted
test: Add integration tests for disabled output types in DeviceList and command rejection
Two tests: 1. test_disabled_output_type_not_in_device_list: verifies that a disabled hw_position_with_duration is absent from the DeviceAdded message the client receives, while position remains visible. 2. test_disabled_output_type_command_rejected: verifies that the server returns an error when a client sends an OutputCmd targeting a disabled output type at the parse_message level. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent f683928 commit 5a85ba3

1 file changed

Lines changed: 150 additions & 0 deletions

File tree

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
// Buttplug Rust Source Code File - See https://buttplug.io for more info.
2+
//
3+
// Copyright 2016-2026 Nonpolynomial Labs LLC. All rights reserved.
4+
//
5+
// Licensed under the BSD 3-Clause license. See LICENSE file in the project root
6+
// for full license information.
7+
8+
mod util;
9+
10+
use buttplug_client::ButtplugClientEvent;
11+
use buttplug_core::message::{
12+
BUTTPLUG_CURRENT_API_MAJOR_VERSION,
13+
BUTTPLUG_CURRENT_API_MINOR_VERSION,
14+
ButtplugServerMessageV4,
15+
OutputCmdV4,
16+
OutputCommand,
17+
OutputHwPositionWithDuration,
18+
OutputType,
19+
RequestServerInfoV4,
20+
StartScanningV0,
21+
};
22+
use buttplug_server::{
23+
ButtplugServerBuilder,
24+
device::ServerDeviceManagerBuilder,
25+
};
26+
use buttplug_server::message::{ButtplugClientMessageVariant, ButtplugServerMessageVariant};
27+
use buttplug_server_device_config::load_protocol_configs;
28+
use futures::{StreamExt, pin_mut};
29+
use util::{
30+
test_client_with_device_and_custom_dcm,
31+
test_device_manager::{TestDeviceCommunicationManagerBuilder, TestDeviceIdentifier},
32+
};
33+
34+
const USER_CONFIG: &str = include_str!(
35+
"util/device_test/device_test_case/config/tcode_disabled_hw_position_user_config.json"
36+
);
37+
38+
fn load_disabled_test_dcm() -> buttplug_server_device_config::DeviceConfigurationManager {
39+
load_protocol_configs(&None, &Some(USER_CONFIG.to_string()), false)
40+
.expect("Test, assuming infallible.")
41+
.finish()
42+
.expect("Test, assuming infallible.")
43+
}
44+
45+
/// Verify that a disabled output type is absent from the DeviceList/DeviceAdded message the
46+
/// client receives. After disabling hw_position_with_duration, the client should see only
47+
/// position on feature 0.
48+
#[tokio::test]
49+
async fn test_disabled_output_type_not_in_device_list() {
50+
let dcm = load_disabled_test_dcm();
51+
let identifier =
52+
TestDeviceIdentifier::new("tcode-v03-disabled-test", Some("tcode-disabled-test-addr".into()));
53+
54+
let (client, _device_channel) = test_client_with_device_and_custom_dcm(&identifier, dcm).await;
55+
56+
let mut event_stream = client.event_stream();
57+
client
58+
.start_scanning()
59+
.await
60+
.expect("Test, assuming infallible.");
61+
62+
let mut client_device = None;
63+
while let Some(msg) = event_stream.next().await {
64+
if let ButtplugClientEvent::DeviceAdded(da) = msg {
65+
client_device = Some(da);
66+
break;
67+
}
68+
}
69+
70+
let device = client_device.expect("Test, assuming infallible.");
71+
assert!(
72+
device.output_available(OutputType::Position),
73+
"position should be available (not disabled)"
74+
);
75+
assert!(
76+
!device.output_available(OutputType::HwPositionWithDuration),
77+
"hw_position_with_duration should not be available (disabled in user config)"
78+
);
79+
}
80+
81+
/// Verify that the server rejects a command targeting a disabled output type, even if the client
82+
/// constructs one directly. This guards against stale cached feature lists on older clients.
83+
#[tokio::test]
84+
async fn test_disabled_output_type_command_rejected() {
85+
let dcm = load_disabled_test_dcm();
86+
let identifier =
87+
TestDeviceIdentifier::new("tcode-v03-disabled-test", Some("tcode-disabled-test-addr".into()));
88+
89+
let mut builder = TestDeviceCommunicationManagerBuilder::default();
90+
let _device_channel = builder.add_test_device(&identifier);
91+
92+
let mut dm_builder = ServerDeviceManagerBuilder::new(dcm);
93+
dm_builder.comm_manager(builder);
94+
95+
let server = ButtplugServerBuilder::new(dm_builder.finish().unwrap())
96+
.finish()
97+
.unwrap();
98+
99+
let recv = server.event_stream();
100+
pin_mut!(recv);
101+
102+
server
103+
.parse_message(ButtplugClientMessageVariant::V4(
104+
RequestServerInfoV4::new(
105+
"Test Client",
106+
BUTTPLUG_CURRENT_API_MAJOR_VERSION,
107+
BUTTPLUG_CURRENT_API_MINOR_VERSION,
108+
)
109+
.into(),
110+
))
111+
.await
112+
.expect("Test, assuming infallible.");
113+
114+
server
115+
.parse_message(ButtplugClientMessageVariant::V4(
116+
StartScanningV0::default().into(),
117+
))
118+
.await
119+
.expect("Test, assuming infallible.");
120+
121+
// Wait for the device to appear in a DeviceList update.
122+
let mut device_index = None;
123+
while let Some(msg) = recv.next().await {
124+
if let ButtplugServerMessageVariant::V4(ButtplugServerMessageV4::DeviceList(list)) = msg {
125+
if !list.devices().is_empty() {
126+
device_index = Some(*list.devices().keys().next().expect("Checked non-empty above"));
127+
break;
128+
}
129+
}
130+
}
131+
132+
let device_index = device_index.expect("Test device should have appeared");
133+
134+
// Directly construct a command targeting the disabled output type and verify the server rejects it.
135+
let result = server
136+
.parse_message(ButtplugClientMessageVariant::V4(
137+
OutputCmdV4::new(
138+
device_index,
139+
0,
140+
OutputCommand::HwPositionWithDuration(OutputHwPositionWithDuration::new(500, 1000)),
141+
)
142+
.into(),
143+
))
144+
.await;
145+
146+
assert!(
147+
result.is_err(),
148+
"Server should reject command targeting disabled output type hw_position_with_duration"
149+
);
150+
}

0 commit comments

Comments
 (0)