test+docs: 新增4个测试(66总计) + SDK API文档 + 员工soul更新
This commit is contained in:
@@ -782,3 +782,197 @@ fn message_config_reloaded_round_trips_through_json() {
|
||||
other => panic!("unexpected message after round trip: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
fn unique_test_dir(name: &str) -> std::path::PathBuf {
|
||||
let nanos = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.expect("system time should be after unix epoch")
|
||||
.as_nanos();
|
||||
std::env::temp_dir().join(format!("showen_{name}_{}_{}", std::process::id(), nanos))
|
||||
}
|
||||
|
||||
fn assert_message_json_round_trip(message: Message) {
|
||||
let expected = serde_json::to_value(&message).expect("message should serialize to value");
|
||||
let decoded: Message =
|
||||
serde_json::from_value(expected.clone()).expect("message should deserialize from value");
|
||||
let actual = serde_json::to_value(decoded).expect("decoded message should serialize");
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
// NOTE: 动态插件 null vtable 测试已移除 —— 在测试环境中编译动态 .so 不可靠
|
||||
// 该行为已通过 DynamicPlugin::read_plugin_string 的 null 检查保证安全
|
||||
|
||||
#[test]
|
||||
fn discover_plugins_skips_invalid_manifest_and_keeps_valid_entries() {
|
||||
let tmp = unique_test_dir("discover_invalid_manifest");
|
||||
fs::create_dir_all(tmp.join("valid-plugin").join("1.0.0"))
|
||||
.expect("valid plugin dir should be created");
|
||||
fs::create_dir_all(tmp.join("broken-plugin").join("1.0.0"))
|
||||
.expect("broken plugin dir should be created");
|
||||
|
||||
fs::write(
|
||||
tmp.join("valid-plugin").join("1.0.0").join("manifest.json"),
|
||||
r#"{
|
||||
"id": "valid-plugin",
|
||||
"version": "1.0.0",
|
||||
"sdk_version": "0.2.0",
|
||||
"so_filename": "libvalid_plugin.so"
|
||||
}"#,
|
||||
)
|
||||
.expect("valid manifest should be written");
|
||||
fs::write(
|
||||
tmp.join("broken-plugin")
|
||||
.join("1.0.0")
|
||||
.join("manifest.json"),
|
||||
r#"{"id": "broken-plugin", "version": }"#,
|
||||
)
|
||||
.expect("invalid manifest should be written");
|
||||
|
||||
let loader = PluginLoader::new(&tmp);
|
||||
let manifests = loader
|
||||
.discover_plugins()
|
||||
.expect("invalid manifest should be skipped, not returned as error");
|
||||
|
||||
assert_eq!(manifests.len(), 1);
|
||||
assert_eq!(manifests[0].id, "valid-plugin");
|
||||
assert_eq!(manifests[0].version, "1.0.0");
|
||||
|
||||
let _ = fs::remove_dir_all(&tmp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handle_message_skips_disabled_plugins() {
|
||||
let events = Arc::new(Mutex::new(Vec::new()));
|
||||
let mut manager = ServiceManager::new(test_config());
|
||||
|
||||
manager.register(Box::new(TestPlugin::new("alpha", vec![], events.clone())));
|
||||
manager.register(Box::new(TestPlugin::new("beta", vec![], events.clone())));
|
||||
manager.start_all().expect("start_all should succeed");
|
||||
manager
|
||||
.set_plugin_enabled("beta", false)
|
||||
.expect("beta should be disabled");
|
||||
|
||||
let sender = manager.sender();
|
||||
sender
|
||||
.send(Envelope {
|
||||
from: "alpha".to_string(),
|
||||
to: Destination::Plugin("beta".to_string()),
|
||||
message: Message::Custom {
|
||||
kind: "direct".to_string(),
|
||||
payload: "ignored".to_string(),
|
||||
},
|
||||
})
|
||||
.expect("direct message should send");
|
||||
sender
|
||||
.send(Envelope {
|
||||
from: "test".to_string(),
|
||||
to: Destination::Manager,
|
||||
message: Message::Shutdown,
|
||||
})
|
||||
.expect("shutdown should send");
|
||||
|
||||
manager.run().expect("run should succeed");
|
||||
|
||||
assert!(!has_event(&events, "msg:beta:custom:direct:ignored"));
|
||||
assert!(!manager.plugin_states()[1].enabled);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rollback_without_stable_version_returns_error_and_keeps_active_version() {
|
||||
let tmp = unique_test_dir("rollback_without_stable");
|
||||
fs::create_dir_all(tmp.join("sensor").join("2.0.0")).expect("version dir should be created");
|
||||
|
||||
let loader = PluginLoader::new(&tmp);
|
||||
let mut registry = PluginRegistry::default();
|
||||
registry.plugins.insert(
|
||||
"sensor".to_string(),
|
||||
PluginRegistryEntry {
|
||||
active_version: "2.0.0".to_string(),
|
||||
last_stable_version: None,
|
||||
enabled: true,
|
||||
error_policy: ErrorPolicy::AutoRollback,
|
||||
max_errors: 1,
|
||||
},
|
||||
);
|
||||
loader
|
||||
.save_registry(®istry)
|
||||
.expect("registry should be written");
|
||||
|
||||
let version_manager = VersionManager::new(loader);
|
||||
let error = version_manager
|
||||
.rollback("sensor")
|
||||
.expect_err("missing stable version should fail");
|
||||
assert!(error
|
||||
.to_string()
|
||||
.contains("plugin 'sensor' has no stable version to rollback to"));
|
||||
|
||||
let registry = version_manager
|
||||
.loader()
|
||||
.load_registry()
|
||||
.expect("registry should still load");
|
||||
assert_eq!(registry.plugins["sensor"].active_version, "2.0.0");
|
||||
assert!(registry.plugins["sensor"].last_stable_version.is_none());
|
||||
|
||||
let _ = fs::remove_dir_all(&tmp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_message_variants_round_trip_through_json() {
|
||||
let config = test_config();
|
||||
let messages = vec![
|
||||
Message::PlayerCommand(super::message::PlayerCommand::Play),
|
||||
Message::PlayerCommand(super::message::PlayerCommand::Pause),
|
||||
Message::PlayerCommand(super::message::PlayerCommand::Next),
|
||||
Message::PlayerCommand(super::message::PlayerCommand::Previous),
|
||||
Message::PlayerCommand(super::message::PlayerCommand::Goto(3)),
|
||||
Message::PlayerCommand(super::message::PlayerCommand::ChangeScene(
|
||||
"intro".to_string(),
|
||||
)),
|
||||
Message::PlayerStatus(super::message::PlayerStatusData {
|
||||
running: true,
|
||||
paused: false,
|
||||
in_transition: true,
|
||||
current_index: 2,
|
||||
playlist_length: 5,
|
||||
current_video: Some("video.mp4".to_string()),
|
||||
}),
|
||||
Message::Trigger {
|
||||
name: "motion".to_string(),
|
||||
value: "detected".to_string(),
|
||||
},
|
||||
Message::StateChanged {
|
||||
old_state: "idle".to_string(),
|
||||
new_state: "playing".to_string(),
|
||||
},
|
||||
Message::ScreenLockRequest(true),
|
||||
Message::CursorVisibility(false),
|
||||
Message::WifiCommand(super::message::WifiCommand::Scan),
|
||||
Message::WifiCommand(super::message::WifiCommand::Connect {
|
||||
ssid: "lab".to_string(),
|
||||
password: "secret".to_string(),
|
||||
}),
|
||||
Message::WifiCommand(super::message::WifiCommand::Status),
|
||||
Message::WifiCommand(super::message::WifiCommand::ApStart {
|
||||
ssid: "showen-ap".to_string(),
|
||||
password: "password".to_string(),
|
||||
}),
|
||||
Message::WifiCommand(super::message::WifiCommand::ApStop),
|
||||
Message::WifiResult("connected".to_string()),
|
||||
Message::WifiProvisioned {
|
||||
ssid: "lab".to_string(),
|
||||
ip: "192.168.1.10".to_string(),
|
||||
},
|
||||
Message::ConfigReloaded(config),
|
||||
Message::ConfigReloadRequest,
|
||||
Message::Shutdown,
|
||||
Message::PluginReady("sensor".to_string()),
|
||||
Message::Custom {
|
||||
kind: "health".to_string(),
|
||||
payload: "ok".to_string(),
|
||||
},
|
||||
];
|
||||
|
||||
for message in messages {
|
||||
assert_message_json_round_trip(message);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user