Initial commit: Update project structure and add new features
This commit is contained in:
Generated
+2943
-31
File diff suppressed because it is too large
Load Diff
@@ -22,4 +22,10 @@ tauri = { version = "2", features = [] }
|
||||
tauri-plugin-opener = "2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
|
||||
tauri-plugin-sql = { version = "2", features = ['sqlite'] }
|
||||
tauri-plugin-notification = "2"
|
||||
iroh = { version = "0.97", features = ["address-lookup-mdns"] }
|
||||
iroh-tickets = "0.4"
|
||||
tokio = "1.50.0"
|
||||
anyhow = "1.0.102"
|
||||
chrono = "0.4"
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
# LocalFinder 实现说明
|
||||
|
||||
## 📋 概述
|
||||
|
||||
已成功构建 `LocalFinder` 结构的基础框架,用于 Iroh 的局域网设备发现功能。由于 Iroh v0.97 API 发生重大变更,当前实现为占位符版本。
|
||||
|
||||
## ✅ 已完成的工作
|
||||
|
||||
### 1. 文件结构
|
||||
|
||||
创建了以下文件:
|
||||
|
||||
- **`src/iroh/local_find.rs`** - LocalFinder 核心实现
|
||||
- **`src/iroh/mod.rs`** - Iroh 模块导出
|
||||
- **Tauri Commands** - 在 `handlers/tauri_handlers.rs` 中添加了 5 个新的命令处理器
|
||||
|
||||
### 2. LocalFinder 结构
|
||||
|
||||
```rust
|
||||
pub struct LocalFinder {
|
||||
node_id: String,
|
||||
}
|
||||
```
|
||||
|
||||
#### 已实现的方法:
|
||||
|
||||
- `new()` - 创建局域网发现管理器(占位符)
|
||||
- `local_id()` - 获取本地节点 ID
|
||||
|
||||
### 3. Tauri Commands
|
||||
|
||||
已添加以下命令供前端调用:
|
||||
|
||||
1. **`initialize_local_finder()`** - 初始化局域网发现管理器
|
||||
2. **`start_discovery()`** - 开始设备发现(占位符)
|
||||
3. **`stop_discovery()`** - 停止设备发现(占位符)
|
||||
4. **`get_local_node_id()`** - 获取本地节点 ID
|
||||
5. **`get_discovered_peers()`** - 获取发现的设备列表(占位符)
|
||||
|
||||
### 4. 集成到应用
|
||||
|
||||
- ✅ 更新了 `lib.rs` 导出 `LocalFinder`
|
||||
- ✅ 在 Tauri 应用中注册了 `LocalFinder` 状态管理
|
||||
- ✅ 将所有新 commands 添加到 invoke_handler
|
||||
|
||||
## ⚠️ 当前限制
|
||||
|
||||
### Iroh API 变更问题
|
||||
|
||||
Iroh v0.97 的 API 与早期版本有很大不同,主要问题包括:
|
||||
|
||||
1. **`Endpoint::builder()`** - 现在需要 `Preset` trait 参数
|
||||
2. **`SecretKey::generate()`** - 需要特定类型的 RNG
|
||||
3. **`MdnsAddressLookup`** - MDNS 发现 API 已变更
|
||||
4. **依赖冲突** - `rand` 和 `rand_core` 版本兼容性问题
|
||||
|
||||
### 占位符实现
|
||||
|
||||
当前代码使用占位符实现,仅用于演示结构和接口设计。
|
||||
|
||||
## 🔧 下一步工作
|
||||
|
||||
### 需要查阅的文档
|
||||
|
||||
1. **Iroh v0.97 官方文档** - https://docs.rs/iroh/latest/iroh/
|
||||
2. **Iroh GitHub 仓库** - https://github.com/n0-computer/iroh
|
||||
3. **Iroh Examples** - 查看最新的示例代码
|
||||
|
||||
### 待实现的功能
|
||||
|
||||
1. **正确的 Endpoint 初始化**
|
||||
```rust
|
||||
// 需要找到正确的方式
|
||||
let endpoint = Endpoint::builder(preset).bind().await?;
|
||||
```
|
||||
|
||||
2. **SecretKey 生成**
|
||||
```rust
|
||||
// 需要使用正确的 RNG
|
||||
let secret_key = SecretKey::generate(/* correct rng */);
|
||||
```
|
||||
|
||||
3. **MDNS 发现集成**
|
||||
```rust
|
||||
// 需要查阅新的 API
|
||||
let mdns = MdnsAddressLookup::new()?;
|
||||
endpoint.discovery().add(mdns)?;
|
||||
```
|
||||
|
||||
4. **设备连接和管理**
|
||||
- `connect_to_peer()` - 连接到发现的设备
|
||||
- `disconnect_peer()` - 断开连接
|
||||
- `get_known_peers()` - 获取已知设备列表
|
||||
|
||||
## 📝 使用示例(当前占位符)
|
||||
|
||||
### Rust 代码
|
||||
|
||||
```rust
|
||||
use crate::iroh::LocalFinder;
|
||||
|
||||
// 创建 LocalFinder
|
||||
let finder = LocalFinder::new().await?;
|
||||
|
||||
// 获取节点 ID
|
||||
let node_id = finder.local_id();
|
||||
println!("Local node ID: {}", node_id);
|
||||
```
|
||||
|
||||
### TypeScript/JavaScript 调用(前端)
|
||||
|
||||
```typescript
|
||||
// 初始化
|
||||
const nodeId = await invoke('initialize_local_finder');
|
||||
console.log(nodeId);
|
||||
|
||||
// 获取本地节点 ID
|
||||
const localId = await invoke('get_local_node_id');
|
||||
console.log(localId);
|
||||
|
||||
// 开始发现(占位符)
|
||||
await invoke('start_discovery', { durationSecs: 60 });
|
||||
|
||||
// 获取发现的设备(占位符)
|
||||
const peers = await invoke('get_discovered_peers');
|
||||
console.log(peers);
|
||||
```
|
||||
|
||||
## 🎯 建议
|
||||
|
||||
由于 Iroh API 频繁变更,建议:
|
||||
|
||||
1. **暂时移除 Iroh 依赖** - 避免编译错误
|
||||
2. **使用条件编译** - 仅在需要时启用 Iroh 功能
|
||||
3. **关注 Iroh 更新** - 订阅 Iroh 的 release notes
|
||||
4. **考虑替代方案** - 如果 Iroh 不稳定,可以考虑其他 P2P 库
|
||||
|
||||
## 📦 依赖配置
|
||||
|
||||
当前 `Cargo.toml` 中的相关依赖:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
iroh = { version = "0.97", features = ["address-lookup-mdns"] }
|
||||
iroh-tickets = "0.4"
|
||||
tokio = "1.50.0"
|
||||
anyhow = "1.0.102"
|
||||
```
|
||||
|
||||
## 📊 项目状态
|
||||
|
||||
- ✅ **LocalFinder 结构** - 完成
|
||||
- ✅ **Tauri 集成** - 完成
|
||||
- ✅ **Commands 实现** - 完成(占位符)
|
||||
- ⚠️ **实际功能** - 等待 Iroh API 研究
|
||||
|
||||
---
|
||||
|
||||
**创建时间**: 2026-04-03
|
||||
**最后更新**: 2026-04-03
|
||||
**状态**: 占位符实现,等待 Iroh API 研究
|
||||
Binary file not shown.
@@ -2,9 +2,13 @@
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "Capability for the main window",
|
||||
"windows": ["main"],
|
||||
"windows": [
|
||||
"main"
|
||||
],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"opener:default"
|
||||
"opener:default",
|
||||
"sql:default",
|
||||
"notification:default"
|
||||
]
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,65 @@
|
||||
use tauri_plugin_sql::{Migration, MigrationKind};
|
||||
|
||||
/// 获取所有数据库迁移
|
||||
pub fn get_migrations() -> Vec<Migration> {
|
||||
vec![
|
||||
// 版本 1: 创建初始表
|
||||
Migration {
|
||||
version: 1,
|
||||
description: "create_initial_tables",
|
||||
sql: r#"
|
||||
-- 用户表
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 工作流表
|
||||
CREATE TABLE IF NOT EXISTS workflows (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
status TEXT DEFAULT 'draft',
|
||||
creator TEXT NOT NULL,
|
||||
definition TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 流程节点表
|
||||
CREATE TABLE IF NOT EXISTS workflow_nodes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
workflow_id INTEGER NOT NULL,
|
||||
node_type TEXT NOT NULL,
|
||||
position_x REAL NOT NULL,
|
||||
position_y REAL NOT NULL,
|
||||
data TEXT,
|
||||
FOREIGN KEY (workflow_id) REFERENCES workflows(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- 流程边表
|
||||
CREATE TABLE IF NOT EXISTS workflow_edges (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
workflow_id INTEGER NOT NULL,
|
||||
source TEXT NOT NULL,
|
||||
target TEXT NOT NULL,
|
||||
label TEXT,
|
||||
data TEXT,
|
||||
FOREIGN KEY (workflow_id) REFERENCES workflows(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- 消息表
|
||||
CREATE TABLE IF NOT EXISTS messages (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
title TEXT NOT NULL,
|
||||
content TEXT,
|
||||
is_read BOOLEAN DEFAULT FALSE,
|
||||
workflow_id INTEGER,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (workflow_id) REFERENCES workflows(id) ON DELETE SET NULL
|
||||
);
|
||||
"#,
|
||||
kind: MigrationKind::Up,
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod migrations;
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod tauri_handlers;
|
||||
|
||||
pub use tauri_handlers::TauriMessageHandler;
|
||||
@@ -0,0 +1,159 @@
|
||||
use crate::router::MessageRouter;
|
||||
use crate::iroh::LocalFinder;
|
||||
use serde_json::Value;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
pub struct TauriMessageHandler {
|
||||
#[allow(dead_code)]
|
||||
router: Arc<Mutex<MessageRouter>>,
|
||||
}
|
||||
|
||||
impl TauriMessageHandler {
|
||||
pub fn new(router: MessageRouter) -> Self {
|
||||
TauriMessageHandler {
|
||||
router: Arc::new(Mutex::new(router)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn send_message(state: tauri::State<'_, Arc<Mutex<MessageRouter>>>, message: Value) -> Result<String, String> {
|
||||
let router = state.lock().await;
|
||||
|
||||
match router.send(message).await {
|
||||
Ok(_) => Ok("Message sent successfully".to_string()),
|
||||
Err(e) => Err(format!("Failed to send message: {}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_supported_message_types() -> Result<Vec<String>, String> {
|
||||
Ok(vec!["text".to_string(), "file".to_string()])
|
||||
}
|
||||
|
||||
/// 初始化局域网发现
|
||||
#[tauri::command]
|
||||
pub async fn initialize_local_finder(
|
||||
state: tauri::State<'_, Arc<Mutex<Option<LocalFinder>>>>,
|
||||
) -> Result<String, String> {
|
||||
let mut finder_opt = state.lock().await;
|
||||
|
||||
if finder_opt.is_some() {
|
||||
return Ok("Local finder already initialized".to_string());
|
||||
}
|
||||
|
||||
match LocalFinder::new().await {
|
||||
Ok(finder) => {
|
||||
let node_id = finder.local_id();
|
||||
*finder_opt = Some(finder);
|
||||
Ok(format!("Local finder initialized successfully. Node ID: {}", node_id))
|
||||
}
|
||||
Err(e) => Err(format!("Failed to initialize local finder: {}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
/// 开始设备发现
|
||||
#[tauri::command]
|
||||
pub async fn start_discovery(
|
||||
_state: tauri::State<'_, Arc<Mutex<Option<LocalFinder>>>>,
|
||||
_duration_secs: u64,
|
||||
) -> Result<String, String> {
|
||||
Ok("Discovery functionality will be implemented in future versions".to_string())
|
||||
}
|
||||
|
||||
/// 停止设备发现
|
||||
#[tauri::command]
|
||||
pub async fn stop_discovery(
|
||||
_state: tauri::State<'_, Arc<Mutex<Option<LocalFinder>>>>,
|
||||
) -> Result<String, String> {
|
||||
Ok("Stop discovery functionality will be implemented in future versions".to_string())
|
||||
}
|
||||
|
||||
/// 获取本地节点 ID
|
||||
#[tauri::command]
|
||||
pub async fn get_local_node_id(
|
||||
state: tauri::State<'_, Arc<Mutex<Option<LocalFinder>>>>,
|
||||
) -> Result<String, String> {
|
||||
let finder_opt = state.lock().await;
|
||||
|
||||
if let Some(finder) = finder_opt.as_ref() {
|
||||
Ok(finder.local_id())
|
||||
} else {
|
||||
Err("Local finder not initialized".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取发现的设备列表
|
||||
#[tauri::command]
|
||||
pub async fn get_discovered_peers(
|
||||
state: tauri::State<'_, Arc<Mutex<Option<LocalFinder>>>>,
|
||||
) -> Result<String, String> {
|
||||
let finder_opt = state.lock().await;
|
||||
|
||||
if let Some(finder) = finder_opt.as_ref() {
|
||||
Ok(format!("Local node ID: {}", finder.local_id()))
|
||||
} else {
|
||||
Err("Local finder not initialized".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::plugins::TextMessagePlugin;
|
||||
use serde_json::json;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_send_message_success() {
|
||||
let (sender, _receiver) = mpsc::channel(100);
|
||||
|
||||
let router = MessageRouter::builder()
|
||||
.register_plugin(TextMessagePlugin::new())
|
||||
.with_iroh_sender(sender)
|
||||
.build();
|
||||
|
||||
let handler = TauriMessageHandler::new(router);
|
||||
let message = json!({
|
||||
"type": "text",
|
||||
"to": "user123",
|
||||
"from": "user456",
|
||||
"content": "Test"
|
||||
});
|
||||
|
||||
let result = handler.send_message(message).await;
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), "Message sent successfully");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_send_message_failure() {
|
||||
// 不设置 iroh_sender,导致发送失败
|
||||
let router = MessageRouter::builder()
|
||||
.register_plugin(TextMessagePlugin::new())
|
||||
.build();
|
||||
|
||||
let handler = TauriMessageHandler::new(router);
|
||||
let message = json!({
|
||||
"type": "text",
|
||||
"to": "user123",
|
||||
"from": "user456",
|
||||
"content": "Test"
|
||||
});
|
||||
|
||||
let result = handler.send_message(message).await;
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_supported_message_types() {
|
||||
let router = MessageRouter::builder().build();
|
||||
let handler = TauriMessageHandler::new(router);
|
||||
|
||||
let result = handler.get_supported_message_types();
|
||||
assert!(result.is_ok());
|
||||
let types = result.unwrap();
|
||||
assert!(types.contains(&"text".to_string()));
|
||||
assert!(types.contains(&"file".to_string()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/// 局域网发现管理器(占位符实现)
|
||||
///
|
||||
/// TODO: 需要查阅最新的 Iroh 文档来正确实现
|
||||
/// Iroh v0.97 的 API 与早期版本有很大不同
|
||||
pub struct LocalFinder {
|
||||
node_id: String,
|
||||
}
|
||||
|
||||
impl LocalFinder {
|
||||
/// 创建新的局域网发现管理器
|
||||
pub async fn new() -> anyhow::Result<Self> {
|
||||
// 占位符实现 - 仅用于演示结构
|
||||
Ok(LocalFinder {
|
||||
node_id: "placeholder-node-id".to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
/// 获取本地节点 ID
|
||||
pub fn local_id(&self) -> String {
|
||||
self.node_id.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for LocalFinder {
|
||||
fn drop(&mut self) {
|
||||
// 清理资源(占位符)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_local_finder_creation() {
|
||||
let finder = LocalFinder::new().await;
|
||||
assert!(finder.is_ok());
|
||||
|
||||
let finder = finder.unwrap();
|
||||
assert!(!finder.local_id().is_empty());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod local_find;
|
||||
|
||||
pub use local_find::LocalFinder;
|
||||
+58
-25
@@ -1,35 +1,68 @@
|
||||
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
||||
#[tauri::command]
|
||||
fn greet(name: &str) -> String {
|
||||
format!("Hello, {}! You've been greeted from Rust!", name)
|
||||
}
|
||||
|
||||
|
||||
// #[tauri::command]
|
||||
// fn something(state: tauri::State<String>) -> String {
|
||||
// state.inner().clone()
|
||||
// }
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use super::something;
|
||||
// use tauri::Manager;
|
||||
|
||||
// #[test]
|
||||
// pub fn test_something() {
|
||||
// let app = tauri::test::mock_app();
|
||||
// app.manage("something".to_string());
|
||||
// assert_eq!(&something(app.state::<String>()), "something");
|
||||
// }
|
||||
// }
|
||||
mod db;
|
||||
mod iroh;
|
||||
mod plugins;
|
||||
mod router;
|
||||
mod handlers;
|
||||
|
||||
pub use plugins::{MessagePlugin, PluginResult};
|
||||
pub use router::MessageRouter;
|
||||
pub use handlers::TauriMessageHandler;
|
||||
pub use plugins::{FileMessagePlugin, TextMessagePlugin};
|
||||
pub use iroh::LocalFinder;
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
let migrations = db::migrations::get_migrations();
|
||||
|
||||
// 创建消息路由器
|
||||
let (iroh_sender, iroh_receiver) = tokio::sync::mpsc::channel(100);
|
||||
|
||||
let router = MessageRouter::builder()
|
||||
.register_plugin(FileMessagePlugin::new())
|
||||
.register_plugin(TextMessagePlugin::new())
|
||||
.with_iroh_sender(iroh_sender)
|
||||
.build();
|
||||
|
||||
// 创建 Tauri 处理器和管理状态
|
||||
let handler_state = Arc::new(Mutex::new(router));
|
||||
|
||||
// 创建局域网发现管理器
|
||||
let local_finder_state: Arc<Mutex<Option<LocalFinder>>> = Arc::new(Mutex::new(None));
|
||||
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_notification::init())
|
||||
.plugin(
|
||||
tauri_plugin_sql::Builder::new()
|
||||
.add_migrations("sqlite:app.db", migrations)
|
||||
.build(),
|
||||
)
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.invoke_handler(tauri::generate_handler![greet])
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
handlers::tauri_handlers::send_message,
|
||||
handlers::tauri_handlers::get_supported_message_types,
|
||||
handlers::tauri_handlers::initialize_local_finder,
|
||||
handlers::tauri_handlers::start_discovery,
|
||||
handlers::tauri_handlers::stop_discovery,
|
||||
handlers::tauri_handlers::get_local_node_id,
|
||||
handlers::tauri_handlers::get_discovered_peers,
|
||||
])
|
||||
.manage(handler_state)
|
||||
.manage(local_finder_state)
|
||||
.setup(|app| {
|
||||
// 启动 Iroh 接收处理任务
|
||||
let _app_handle = app.handle();
|
||||
let mut iroh_receiver = iroh_receiver;
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
while let Some(_binary_data) = iroh_receiver.recv().await {
|
||||
println!("Received binary data from Iroh: {} bytes", _binary_data.len());
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
use super::{BinaryData, MessagePlugin, PluginResult};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
/// 文件消息插件
|
||||
pub struct FileMessagePlugin;
|
||||
|
||||
impl FileMessagePlugin {
|
||||
pub fn new() -> Self {
|
||||
FileMessagePlugin {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FileMessagePlugin {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl MessagePlugin for FileMessagePlugin {
|
||||
fn get_plugin_type(&self) -> &str {
|
||||
"file"
|
||||
}
|
||||
|
||||
fn before_send(&self, json_data: &Value) -> PluginResult<BinaryData> {
|
||||
let to = json_data.get("to")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or("Missing 'to' field")?;
|
||||
|
||||
let from = json_data.get("from")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or("Missing 'from' field")?;
|
||||
|
||||
let processed_data = json!({
|
||||
"type": "file",
|
||||
"to": to,
|
||||
"from": from,
|
||||
"timestamp": chrono::Utc::now().timestamp(),
|
||||
});
|
||||
|
||||
Ok(serde_json::to_vec(&processed_data)?)
|
||||
}
|
||||
|
||||
fn before_receive(&self, binary_data: &BinaryData) -> PluginResult<Value> {
|
||||
let json_value: Value = serde_json::from_slice(binary_data)?;
|
||||
Ok(json_value)
|
||||
}
|
||||
|
||||
fn after_send(&self, _json_data: &Value) -> PluginResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn after_receive(&self, _binary_data: &BinaryData) -> PluginResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn test_get_plugin_type() {
|
||||
let plugin = FileMessagePlugin::new();
|
||||
assert_eq!(plugin.get_plugin_type(), "file");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_before_send_valid_message() {
|
||||
let plugin = FileMessagePlugin::new();
|
||||
let message = json!({
|
||||
"type": "file",
|
||||
"to": "user123",
|
||||
"from": "user456"
|
||||
});
|
||||
|
||||
let result = plugin.before_send(&message);
|
||||
assert!(result.is_ok());
|
||||
|
||||
let binary_data = result.unwrap();
|
||||
let parsed: Value = serde_json::from_slice(&binary_data).unwrap();
|
||||
|
||||
assert_eq!(parsed["type"], "file");
|
||||
assert_eq!(parsed["to"], "user123");
|
||||
assert_eq!(parsed["from"], "user456");
|
||||
assert!(parsed["timestamp"].is_number());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_before_send_missing_to_field() {
|
||||
let plugin = FileMessagePlugin::new();
|
||||
let message = json!({
|
||||
"type": "file",
|
||||
"from": "user456"
|
||||
});
|
||||
|
||||
let result = plugin.before_send(&message);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_before_send_missing_from_field() {
|
||||
let plugin = FileMessagePlugin::new();
|
||||
let message = json!({
|
||||
"type": "file",
|
||||
"to": "user123"
|
||||
});
|
||||
|
||||
let result = plugin.before_send(&message);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_before_receive() {
|
||||
let plugin = FileMessagePlugin::new();
|
||||
let original_message = json!({
|
||||
"type": "file",
|
||||
"to": "user123",
|
||||
"from": "user456",
|
||||
"timestamp": 1234567890
|
||||
});
|
||||
|
||||
let binary_data = serde_json::to_vec(&original_message).unwrap();
|
||||
let result = plugin.before_receive(&binary_data);
|
||||
|
||||
assert!(result.is_ok());
|
||||
let received = result.unwrap();
|
||||
assert_eq!(received["type"], "file");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
use serde_json::Value;
|
||||
use std::error::Error;
|
||||
|
||||
pub mod file_plugin;
|
||||
pub mod text_plugin;
|
||||
|
||||
pub use file_plugin::FileMessagePlugin;
|
||||
pub use text_plugin::TextMessagePlugin;
|
||||
|
||||
pub type PluginResult<T> = Result<T, Box<dyn Error>>;
|
||||
pub type BinaryData = Vec<u8>;
|
||||
|
||||
pub trait MessagePlugin: Send + Sync {
|
||||
/// 获取插件类型标识
|
||||
fn get_plugin_type(&self) -> &str;
|
||||
|
||||
/// 发送消息前的处理
|
||||
fn before_send(&self, json_data: &Value) -> PluginResult<BinaryData>;
|
||||
|
||||
/// 接收消息前的处理
|
||||
fn before_receive(&self, binary_data: &BinaryData) -> PluginResult<Value>;
|
||||
|
||||
/// 发送消息后的处理
|
||||
fn after_send(&self, json_data: &Value) -> PluginResult<()>;
|
||||
|
||||
/// 接收消息后的处理
|
||||
fn after_receive(&self, binary_data: &BinaryData) -> PluginResult<()>;
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
use super::{BinaryData, MessagePlugin, PluginResult};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
/// 文本消息插件
|
||||
pub struct TextMessagePlugin;
|
||||
|
||||
impl TextMessagePlugin {
|
||||
pub fn new() -> Self {
|
||||
TextMessagePlugin {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TextMessagePlugin {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl MessagePlugin for TextMessagePlugin {
|
||||
fn get_plugin_type(&self) -> &str {
|
||||
"text"
|
||||
}
|
||||
|
||||
fn before_send(&self, json_data: &Value) -> PluginResult<BinaryData> {
|
||||
let to = json_data.get("to")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or("Missing 'to' field")?;
|
||||
|
||||
let from = json_data.get("from")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or("Missing 'from' field")?;
|
||||
|
||||
let content = json_data.get("content")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or("Missing 'content' field")?;
|
||||
|
||||
let processed_data = json!({
|
||||
"type": "text",
|
||||
"to": to,
|
||||
"from": from,
|
||||
"content": content,
|
||||
"timestamp": chrono::Utc::now().timestamp(),
|
||||
});
|
||||
|
||||
Ok(serde_json::to_vec(&processed_data)?)
|
||||
}
|
||||
|
||||
fn before_receive(&self, binary_data: &BinaryData) -> PluginResult<Value> {
|
||||
let json_value: Value = serde_json::from_slice(binary_data)?;
|
||||
Ok(json_value)
|
||||
}
|
||||
|
||||
fn after_send(&self, _json_data: &Value) -> PluginResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn after_receive(&self, _binary_data: &BinaryData) -> PluginResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn test_get_plugin_type() {
|
||||
let plugin = TextMessagePlugin::new();
|
||||
assert_eq!(plugin.get_plugin_type(), "text");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_before_send_valid_message() {
|
||||
let plugin = TextMessagePlugin::new();
|
||||
let message = json!({
|
||||
"type": "text",
|
||||
"to": "user123",
|
||||
"from": "user456",
|
||||
"content": "Hello, World!"
|
||||
});
|
||||
|
||||
let result = plugin.before_send(&message);
|
||||
assert!(result.is_ok());
|
||||
|
||||
let binary_data = result.unwrap();
|
||||
let parsed: Value = serde_json::from_slice(&binary_data).unwrap();
|
||||
|
||||
assert_eq!(parsed["type"], "text");
|
||||
assert_eq!(parsed["to"], "user123");
|
||||
assert_eq!(parsed["from"], "user456");
|
||||
assert_eq!(parsed["content"], "Hello, World!");
|
||||
assert!(parsed["timestamp"].is_number());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_before_send_missing_to_field() {
|
||||
let plugin = TextMessagePlugin::new();
|
||||
let message = json!({
|
||||
"type": "text",
|
||||
"from": "user456",
|
||||
"content": "Hello"
|
||||
});
|
||||
|
||||
let result = plugin.before_send(&message);
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().to_string().contains("Missing 'to' field"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_before_send_missing_from_field() {
|
||||
let plugin = TextMessagePlugin::new();
|
||||
let message = json!({
|
||||
"type": "text",
|
||||
"to": "user123",
|
||||
"content": "Hello"
|
||||
});
|
||||
|
||||
let result = plugin.before_send(&message);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_before_send_missing_content_field() {
|
||||
let plugin = TextMessagePlugin::new();
|
||||
let message = json!({
|
||||
"type": "text",
|
||||
"to": "user123",
|
||||
"from": "user456"
|
||||
});
|
||||
|
||||
let result = plugin.before_send(&message);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_before_receive() {
|
||||
let plugin = TextMessagePlugin::new();
|
||||
let original_message = json!({
|
||||
"type": "text",
|
||||
"to": "user123",
|
||||
"from": "user456",
|
||||
"content": "Test message",
|
||||
"timestamp": 1234567890
|
||||
});
|
||||
|
||||
let binary_data = serde_json::to_vec(&original_message).unwrap();
|
||||
let result = plugin.before_receive(&binary_data);
|
||||
|
||||
assert!(result.is_ok());
|
||||
let received = result.unwrap();
|
||||
assert_eq!(received["type"], "text");
|
||||
assert_eq!(received["content"], "Test message");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_after_send_and_receive() {
|
||||
let plugin = TextMessagePlugin::new();
|
||||
let message = json!({"test": "data"});
|
||||
let binary_data = vec![1, 2, 3];
|
||||
|
||||
assert!(plugin.after_send(&message).is_ok());
|
||||
assert!(plugin.after_receive(&binary_data).is_ok());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
use crate::plugins::{BinaryData, MessagePlugin};
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
type MessageCallback = Box<dyn Fn(Value) + Send + Sync + 'static>;
|
||||
|
||||
/// 消息路由器
|
||||
pub struct MessageRouter {
|
||||
plugins: Arc<RwLock<HashMap<String, Arc<dyn MessagePlugin>>>>,
|
||||
message_callback: Arc<RwLock<Option<MessageCallback>>>,
|
||||
iroh_sender: Option<mpsc::Sender<BinaryData>>,
|
||||
}
|
||||
|
||||
pub struct MessageRouterBuilder {
|
||||
router: MessageRouter,
|
||||
}
|
||||
|
||||
impl MessageRouter {
|
||||
pub fn builder() -> MessageRouterBuilder {
|
||||
MessageRouterBuilder {
|
||||
router: MessageRouter {
|
||||
plugins: Arc::new(RwLock::new(HashMap::new())),
|
||||
message_callback: Arc::new(RwLock::new(None)),
|
||||
iroh_sender: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// 发送消息
|
||||
pub async fn send(&self, message: Value) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let message_type = message.get("type")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or("Message must have a 'type' field")?;
|
||||
|
||||
// 先获取插件并释放锁
|
||||
let plugin = {
|
||||
let plugins = self.plugins.read().map_err(|_| "Failed to read plugins")?;
|
||||
let plugin = plugins.get(message_type)
|
||||
.ok_or_else(|| format!("No plugin found for message type: {}", message_type))?;
|
||||
Arc::clone(plugin)
|
||||
};
|
||||
|
||||
// 在锁释放后处理消息
|
||||
let processed_data = plugin.before_send(&message)?;
|
||||
|
||||
if let Some(sender) = &self.iroh_sender {
|
||||
sender.send(processed_data).await?;
|
||||
} else {
|
||||
return Err("Iroh sender not initialized".into());
|
||||
}
|
||||
|
||||
plugin.after_send(&message)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 处理接收到的消息
|
||||
pub fn handle_received_message(&self, binary_data: BinaryData) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let json_value: Value = serde_json::from_slice(&binary_data)?;
|
||||
let message_type = json_value.get("type")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or("Received message must have a 'type' field")?;
|
||||
|
||||
// 先获取插件并释放锁
|
||||
let plugin = {
|
||||
let plugins = self.plugins.read().map_err(|_| "Failed to read plugins")?;
|
||||
let plugin = plugins.get(message_type)
|
||||
.ok_or_else(|| format!("No plugin found for message type: {}", message_type))?;
|
||||
Arc::clone(plugin)
|
||||
};
|
||||
|
||||
let processed_message = plugin.before_receive(&binary_data)?;
|
||||
|
||||
if let Some(callback) = &*self.message_callback.read().map_err(|_| "Failed to read callback")? {
|
||||
callback(processed_message);
|
||||
}
|
||||
|
||||
plugin.after_receive(&binary_data)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageRouterBuilder {
|
||||
/// 注册插件
|
||||
pub fn register_plugin<P>(self, plugin: P) -> Self
|
||||
where
|
||||
P: MessagePlugin + 'static,
|
||||
{
|
||||
let plugin_type = plugin.get_plugin_type().to_string();
|
||||
self.router.plugins.write()
|
||||
.expect("Failed to write plugins")
|
||||
.insert(plugin_type, Arc::new(plugin));
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// 设置消息回调
|
||||
pub fn on_message<F>(self, callback: F) -> Self
|
||||
where
|
||||
F: Fn(Value) + Send + Sync + 'static,
|
||||
{
|
||||
*self.router.message_callback.write()
|
||||
.expect("Failed to write callback") = Some(Box::new(callback));
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// 设置 Iroh 发送器
|
||||
pub fn with_iroh_sender(mut self, sender: mpsc::Sender<BinaryData>) -> Self {
|
||||
self.router.iroh_sender = Some(sender);
|
||||
self
|
||||
}
|
||||
|
||||
/// 构建路由器
|
||||
pub fn build(self) -> MessageRouter {
|
||||
self.router
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::plugins::TextMessagePlugin;
|
||||
use serde_json::json;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[test]
|
||||
fn test_router_builder() {
|
||||
let router = MessageRouter::builder()
|
||||
.register_plugin(TextMessagePlugin::new())
|
||||
.build();
|
||||
|
||||
let plugins = router.plugins.read().unwrap();
|
||||
assert!(plugins.contains_key("text"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_register_multiple_plugins() {
|
||||
use crate::plugins::FileMessagePlugin;
|
||||
|
||||
let router = MessageRouter::builder()
|
||||
.register_plugin(TextMessagePlugin::new())
|
||||
.register_plugin(FileMessagePlugin::new())
|
||||
.build();
|
||||
|
||||
let plugins = router.plugins.read().unwrap();
|
||||
assert!(plugins.contains_key("text"));
|
||||
assert!(plugins.contains_key("file"));
|
||||
assert_eq!(plugins.len(), 2);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_send_text_message() {
|
||||
let (sender, _receiver) = mpsc::channel(100);
|
||||
|
||||
let router = MessageRouter::builder()
|
||||
.register_plugin(TextMessagePlugin::new())
|
||||
.with_iroh_sender(sender)
|
||||
.build();
|
||||
|
||||
let message = json!({
|
||||
"type": "text",
|
||||
"to": "user123",
|
||||
"from": "user456",
|
||||
"content": "Test message"
|
||||
});
|
||||
|
||||
let result = router.send(message).await;
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_send_message_without_iroh_sender() {
|
||||
let router = MessageRouter::builder()
|
||||
.register_plugin(TextMessagePlugin::new())
|
||||
.build();
|
||||
|
||||
let message = json!({
|
||||
"type": "text",
|
||||
"to": "user123",
|
||||
"from": "user456",
|
||||
"content": "Test"
|
||||
});
|
||||
|
||||
let result = router.send(message).await;
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().to_string().contains("Iroh sender not initialized"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_send_message_with_unknown_type() {
|
||||
let (sender, _receiver) = mpsc::channel(100);
|
||||
|
||||
let router = MessageRouter::builder()
|
||||
.register_plugin(TextMessagePlugin::new())
|
||||
.with_iroh_sender(sender)
|
||||
.build();
|
||||
|
||||
let message = json!({
|
||||
"type": "unknown",
|
||||
"to": "user123",
|
||||
"from": "user456"
|
||||
});
|
||||
|
||||
let result = router.send(message).await;
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().to_string().contains("No plugin found"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handle_received_message() {
|
||||
let received_messages = Arc::new(Mutex::new(Vec::new()));
|
||||
let received_clone = Arc::clone(&received_messages);
|
||||
|
||||
let router = MessageRouter::builder()
|
||||
.register_plugin(TextMessagePlugin::new())
|
||||
.on_message(move |msg| {
|
||||
received_clone.lock().unwrap().push(msg);
|
||||
})
|
||||
.build();
|
||||
|
||||
let original_message = json!({
|
||||
"type": "text",
|
||||
"to": "user123",
|
||||
"from": "user456",
|
||||
"content": "Incoming message"
|
||||
});
|
||||
|
||||
let binary_data = serde_json::to_vec(&original_message).unwrap();
|
||||
let result = router.handle_received_message(binary_data);
|
||||
|
||||
assert!(result.is_ok());
|
||||
|
||||
let messages = received_messages.lock().unwrap();
|
||||
assert_eq!(messages.len(), 1);
|
||||
assert_eq!(messages[0]["type"], "text");
|
||||
assert_eq!(messages[0]["content"], "Incoming message");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handle_received_message_invalid_type() {
|
||||
let router = MessageRouter::builder()
|
||||
.register_plugin(TextMessagePlugin::new())
|
||||
.build();
|
||||
|
||||
// 发送一个没有 type 字段的消息
|
||||
let invalid_message = vec![1, 2, 3]; // 无效的 JSON
|
||||
let result = router.handle_received_message(invalid_message);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod message_router;
|
||||
|
||||
pub use message_router::MessageRouter;
|
||||
@@ -14,7 +14,9 @@
|
||||
{
|
||||
"title": "rolegram",
|
||||
"width": 800,
|
||||
"height": 600
|
||||
"height": 600,
|
||||
"minWidth": 600,
|
||||
"minHeight": 500
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,213 @@
|
||||
// 消息路由系统测试示例
|
||||
// 这些代码展示了如何测试各个组件
|
||||
|
||||
#[cfg(test)]
|
||||
mod integration_tests {
|
||||
use serde_json::json;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
// 导入被测试的模块
|
||||
use crate::plugins::{MessagePlugin, TextMessagePlugin, FileMessagePlugin};
|
||||
use crate::router::MessageRouter;
|
||||
use crate::handlers::TauriMessageHandler;
|
||||
|
||||
/// 集成测试:完整的消息发送流程
|
||||
#[tokio::test]
|
||||
async fn test_full_message_flow() {
|
||||
// 1. 创建 channel
|
||||
let (sender, mut receiver) = mpsc::channel(100);
|
||||
|
||||
// 2. 记录接收到的消息
|
||||
let received_messages = Arc::new(Mutex::new(Vec::new()));
|
||||
let received_clone = Arc::clone(&received_messages);
|
||||
|
||||
// 3. 创建路由器
|
||||
let router = MessageRouter::builder()
|
||||
.register_plugin(TextMessagePlugin::new())
|
||||
.on_message(move |msg| {
|
||||
received_clone.lock().unwrap().push(msg);
|
||||
})
|
||||
.with_iroh_sender(sender)
|
||||
.build();
|
||||
|
||||
// 4. 发送消息
|
||||
let message = json!({
|
||||
"type": "text",
|
||||
"to": "user123",
|
||||
"from": "user456",
|
||||
"content": "Integration test message"
|
||||
});
|
||||
|
||||
let result = router.send(message.clone()).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
// 5. 验证消息被发送到 channel
|
||||
let sent_data = receiver.recv().await.unwrap();
|
||||
let sent_json: serde_json::Value = serde_json::from_slice(&sent_data).unwrap();
|
||||
assert_eq!(sent_json["type"], "text");
|
||||
assert_eq!(sent_json["content"], "Integration test message");
|
||||
|
||||
// 6. 模拟接收消息
|
||||
let binary_data = serde_json::to_vec(&sent_json).unwrap();
|
||||
let result = router.handle_received_message(binary_data);
|
||||
assert!(result.is_ok());
|
||||
|
||||
// 7. 验证回调被调用
|
||||
let messages = received_messages.lock().unwrap();
|
||||
assert_eq!(messages.len(), 1);
|
||||
assert_eq!(messages[0]["content"], "Integration test message");
|
||||
}
|
||||
|
||||
/// 测试多插件协作
|
||||
#[tokio::test]
|
||||
async fn test_multiple_plugins() {
|
||||
let (sender, _receiver) = mpsc::channel(100);
|
||||
|
||||
let router = MessageRouter::builder()
|
||||
.register_plugin(TextMessagePlugin::new())
|
||||
.register_plugin(FileMessagePlugin::new())
|
||||
.with_iroh_sender(sender)
|
||||
.build();
|
||||
|
||||
// 发送文本消息
|
||||
let text_msg = json!({
|
||||
"type": "text",
|
||||
"to": "user1",
|
||||
"from": "user2",
|
||||
"content": "Text"
|
||||
});
|
||||
assert!(router.send(text_msg).await.is_ok());
|
||||
|
||||
// 发送文件消息
|
||||
let file_msg = json!({
|
||||
"type": "file",
|
||||
"to": "user1",
|
||||
"from": "user2"
|
||||
});
|
||||
assert!(router.send(file_msg).await.is_ok());
|
||||
}
|
||||
|
||||
/// 测试错误处理
|
||||
#[tokio::test]
|
||||
async fn test_error_handling() {
|
||||
let (sender, _receiver) = mpsc::channel(100);
|
||||
|
||||
let router = MessageRouter::builder()
|
||||
.register_plugin(TextMessagePlugin::new())
|
||||
.with_iroh_sender(sender)
|
||||
.build();
|
||||
|
||||
// 测试缺失必要字段
|
||||
let invalid_msg = json!({
|
||||
"type": "text",
|
||||
"content": "Missing to and from"
|
||||
});
|
||||
let result = router.send(invalid_msg).await;
|
||||
assert!(result.is_err());
|
||||
|
||||
// 测试未知消息类型
|
||||
let unknown_type = json!({
|
||||
"type": "unknown_type",
|
||||
"to": "user1",
|
||||
"from": "user2"
|
||||
});
|
||||
let result = router.send(unknown_type).await;
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
/// 测试 Handler 层
|
||||
#[tokio::test]
|
||||
async fn test_handler_layer() {
|
||||
let (sender, _receiver) = mpsc::channel(100);
|
||||
|
||||
let router = MessageRouter::builder()
|
||||
.register_plugin(TextMessagePlugin::new())
|
||||
.with_iroh_sender(sender)
|
||||
.build();
|
||||
|
||||
let handler = TauriMessageHandler::new(router);
|
||||
|
||||
// 测试成功的命令调用
|
||||
let message = json!({
|
||||
"type": "text",
|
||||
"to": "user1",
|
||||
"from": "user2",
|
||||
"content": "Handler test"
|
||||
});
|
||||
|
||||
let result = handler.send_message(message).await;
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), "Message sent successfully");
|
||||
|
||||
// 测试获取支持的消息类型
|
||||
let types = handler.get_supported_message_types().unwrap();
|
||||
assert!(types.contains(&"text".to_string()));
|
||||
}
|
||||
|
||||
/// 性能测试:大量消息发送
|
||||
#[tokio::test]
|
||||
async fn test_performance_many_messages() {
|
||||
let (sender, mut receiver) = mpsc::channel(1000);
|
||||
|
||||
let router = MessageRouter::builder()
|
||||
.register_plugin(TextMessagePlugin::new())
|
||||
.with_iroh_sender(sender)
|
||||
.build();
|
||||
|
||||
// 发送 100 条消息
|
||||
for i in 0..100 {
|
||||
let message = json!({
|
||||
"type": "text",
|
||||
"to": "user1",
|
||||
"from": "user2",
|
||||
"content": format!("Message {}", i)
|
||||
});
|
||||
|
||||
let result = router.send(message).await;
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
// 验证所有消息都被发送
|
||||
let mut count = 0;
|
||||
while let Ok(_) = receiver.try_recv() {
|
||||
count += 1;
|
||||
}
|
||||
assert_eq!(count, 100);
|
||||
}
|
||||
|
||||
/// 测试并发安全性
|
||||
#[tokio::test]
|
||||
async fn test_concurrent_safety() {
|
||||
let (sender, _receiver) = mpsc::channel(100);
|
||||
|
||||
let router = Arc::new(
|
||||
MessageRouter::builder()
|
||||
.register_plugin(TextMessagePlugin::new())
|
||||
.with_iroh_sender(sender)
|
||||
.build()
|
||||
);
|
||||
|
||||
// 创建多个并发任务
|
||||
let mut handles = vec![];
|
||||
for i in 0..10 {
|
||||
let router_clone = Arc::clone(&router);
|
||||
let handle = tokio::spawn(async move {
|
||||
let message = json!({
|
||||
"type": "text",
|
||||
"to": "user1",
|
||||
"from": "user2",
|
||||
"content": format!("Concurrent message {}", i)
|
||||
});
|
||||
router_clone.send(message).await
|
||||
});
|
||||
handles.push(handle);
|
||||
}
|
||||
|
||||
// 等待所有任务完成
|
||||
for handle in handles {
|
||||
let result = handle.await.unwrap();
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user