mod common;
use crate::common::{
test_device_capabilities, test_device_id, test_text_message, NoOpMessageHandler,
};
use std::collections::HashMap;
use std::sync::Arc;
use xlink::capability::manager::CapabilityManager;
use xlink::core::types::{ChannelType, DeviceCapabilities, DeviceType, MessagePayload};
use xlink::heartbeat::manager::HeartbeatManager;
use xlink::router::scoring::Scorer;
use xlink::router::selector::Router;
#[tokio::test]
async fn test_router_scoring_logic() {
let caps = test_device_capabilities();
let message = test_text_message("test");
let state = xlink::core::types::ChannelState {
available: true,
rtt_ms: 50,
jitter_ms: 5,
packet_loss_rate: 0.01,
bandwidth_bps: 1000000,
signal_strength: Some(-50),
distance_meters: Some(5.0),
network_type: xlink::core::types::NetworkType::WiFi,
failure_count: 0,
last_heartbeat: 0,
};
let score = Scorer::score(ChannelType::BluetoothLE, &state, &caps, message.priority);
assert!(score > 0.0);
}
#[tokio::test]
async fn test_router_cost_sensitive() {
let mut caps = test_device_capabilities();
caps.data_cost_sensitive = true;
let cap_manager = Arc::new(CapabilityManager::new(caps));
let target_device = test_device_id();
let state = xlink::core::types::ChannelState {
available: true,
rtt_ms: 50,
jitter_ms: 5,
packet_loss_rate: 0.01,
bandwidth_bps: 1000000,
signal_strength: Some(-50),
distance_meters: Some(5.0),
network_type: xlink::core::types::NetworkType::WiFi,
failure_count: 0,
last_heartbeat: 0,
};
cap_manager.update_channel_state(target_device, ChannelType::BluetoothLE, state);
let mut channels: HashMap<ChannelType, Arc<dyn xlink::core::traits::Channel>> = HashMap::new();
let ble_channel = Arc::new(
xlink::channels::memory::MemoryChannel::new(Arc::new(NoOpMessageHandler), 10)
.with_type(ChannelType::BluetoothLE),
);
channels.insert(ChannelType::BluetoothLE, ble_channel.clone());
let router = Router::new(channels, cap_manager);
let mut msg = test_text_message("test cost");
msg.recipient = target_device;
let selected = router.select_channel(&msg).await.unwrap();
assert_eq!(selected.channel_type(), ChannelType::BluetoothLE);
}
#[tokio::test]
async fn test_capability_detection() {
let caps = DeviceCapabilities {
device_id: test_device_id(),
device_type: DeviceType::Smartphone,
device_name: "Test".to_string(),
supported_channels: [ChannelType::BluetoothLE].into_iter().collect(),
battery_level: Some(75),
is_charging: true,
data_cost_sensitive: false,
};
let manager = CapabilityManager::new(caps);
let detected = manager.get_local_caps();
assert!(detected
.supported_channels
.contains(&ChannelType::BluetoothLE));
assert_eq!(detected.battery_level, Some(75));
}
#[tokio::test]
async fn test_heartbeat_ping_pong() {
let d1 = test_device_id();
let d2 = test_device_id();
let cap_manager = Arc::new(CapabilityManager::new(test_device_capabilities()));
let router = Arc::new(Router::new(HashMap::new(), cap_manager.clone()));
let heartbeat_manager = HeartbeatManager::new(d1, router, cap_manager);
let ping = xlink::core::types::Message {
id: uuid::Uuid::new_v4(),
sender: d2,
recipient: d1,
group_id: None,
payload: MessagePayload::Ping(12345),
timestamp: 12345,
priority: xlink::core::types::MessagePriority::Normal,
require_ack: false,
};
heartbeat_manager.handle_heartbeat(&ping).await;
}
#[test]
fn test_error_code_parsing() {
use xlink::core::error::ErrorCode;
let code: Result<ErrorCode, _> = "101".parse();
assert_eq!(code, Ok(ErrorCode(101)));
let code: Result<ErrorCode, _> = "9999".parse();
assert_eq!(code, Ok(ErrorCode(9999)));
let code: Result<ErrorCode, _> = "10000".parse();
assert!(code.is_err());
}
#[test]
fn test_error_code_module_and_sequence() {
use xlink::core::error::ErrorCode;
let code = ErrorCode(101);
assert_eq!(code.module(), 1);
assert_eq!(code.sequence(), 1);
let code = ErrorCode(205);
assert_eq!(code.module(), 2);
assert_eq!(code.sequence(), 5);
}
#[test]
fn test_error_category_code_range() {
use xlink::core::error::ErrorCategory;
let (start, end) = ErrorCategory::System.code_range();
assert_eq!(start, 100);
assert_eq!(end, 199);
let (start, end) = ErrorCategory::Channel.code_range();
assert_eq!(start, 200);
assert_eq!(end, 299);
}
#[test]
fn test_error_creation() {
use xlink::core::error::XLinkError;
let error = XLinkError::device_not_found("test-device", "test.rs");
assert_eq!(error.code().0, 501);
assert_eq!(error.message(), "设备未找到");
assert_eq!(error.location(), "test.rs");
}
#[test]
fn test_error_with_context() {
use xlink::core::error::XLinkError;
let error =
XLinkError::channel_disconnected("Connection lost", "test.rs").with_device_id("device-123");
assert_eq!(error.context.device_id, Some("device-123".to_string()));
assert!(error.is_retryable());
}
#[test]
fn test_error_chain() {
use xlink::core::error::XLinkError;
let inner = XLinkError::invalid_input("test", "Invalid value", "inner.rs");
let outer = XLinkError::storage_write_failed("key", "Failed", "outer.rs").with_source(inner);
assert!(outer.source.is_some());
assert_eq!(outer.source.as_ref().unwrap().message(), "输入参数无效");
}
#[test]
fn test_crypto_engine_creation() {
use xlink::crypto::engine::CryptoEngine;
let engine = CryptoEngine::new();
let public_key = engine.public_key();
assert_ne!(public_key.as_bytes(), &[0u8; 32]);
}
#[test]
fn test_crypto_sign_and_verify() {
use xlink::crypto::engine::CryptoEngine;
let engine = CryptoEngine::new();
let data = b"test data";
let signature = engine.sign(data);
assert_eq!(signature.len(), 64);
}
#[test]
fn test_session_creation() {
use x25519_dalek::PublicKey;
use xlink::core::types::DeviceId;
use xlink::crypto::engine::CryptoEngine;
let engine = CryptoEngine::new();
let peer_id = DeviceId::new();
let peer_public = PublicKey::from([1u8; 32]);
let result = engine.establish_session(peer_id, peer_public);
assert!(result.is_ok());
}
#[test]
fn test_session_expiration() {
use x25519_dalek::PublicKey;
use xlink::core::types::DeviceId;
use xlink::crypto::engine::CryptoEngine;
let engine = CryptoEngine::new();
let peer_id = DeviceId::new();
let peer_public = PublicKey::from([1u8; 32]);
engine.establish_session(peer_id, peer_public).unwrap();
let sessions = engine.export_state().unwrap();
assert!(!sessions.sessions.is_empty());
}
#[test]
fn test_path_validation() {
use std::path::Path;
let valid_path = Path::new("/tmp/xlink");
assert!(!valid_path.to_string_lossy().contains(".."));
let traversal_path = Path::new("/tmp/xlink/../../../etc");
assert!(traversal_path.to_string_lossy().contains(".."));
}