Skip to main content

agentlink_sdk/
client.rs

1//! AgentLink SDK Client
2//!
3//! The main entry point for the AgentLink SDK.
4
5use std::future::Future;
6use std::sync::Arc;
7
8use tokio::sync::RwLock;
9
10use crate::error::{SdkError, SdkResult};
11use crate::events::event_loop::EventLoop;
12use crate::events::ServerEvent;
13use crate::http::HttpClient;
14use crate::mqtt::client::{MqttClient, MqttConnectionState, MqttEvent};
15
16use crate::services::{
17    AuthService, ConversationService, FriendService, MessageService, UserService,
18};
19
20// =============================================================================
21// 环境变量名称常量
22// =============================================================================
23
24/// API Key 环境变量名称
25pub const ENV_API_KEY: &str = "AGENTLINK_API_KEY";
26
27/// API URL 环境变量名称
28pub const ENV_API_URL: &str = "AGENTLINK_API_URL";
29
30/// 默认 API URL(不包含 /api/v1 路径)
31pub const DEFAULT_API_URL: &str = "https://agentlink-api.feedecho.xyz";
32
33/// API 路径前缀
34pub const API_PATH_PREFIX: &str = "/api/v1";
35
36/// 默认 MQTT 端口
37pub const DEFAULT_MQTT_PORT: u16 = 8883;
38
39// =============================================================================
40// 辅助函数
41// =============================================================================
42
43/// 从 API URL 推导 MQTT Broker URL
44///
45/// 例如:
46/// - `https://agentlink-api.feedecho.xyz` -> `mqtts://mqtt.feedecho.xyz:8883`
47/// - `http://localhost:9600` -> `mqtts://mqtt.localhost:8883`
48fn derive_mqtt_url_from_api_url(api_url: &str) -> String {
49    // 移除协议前缀
50    let host = api_url
51        .strip_prefix("https://")
52        .or_else(|| api_url.strip_prefix("http://"))
53        .unwrap_or(api_url);
54
55    // 移除端口号(如果有)
56    let host = host.split(':').next().unwrap_or(host);
57
58    // 移除路径(如果有)
59    let host = host.split('/').next().unwrap_or(host);
60
61    // 提取主域名(假设格式为 xxx.domain.tld)
62    // 例如:agentlink-api.feedecho.xyz -> feedecho.xyz
63    let parts: Vec<&str> = host.split('.').collect();
64    let base_domain = if parts.len() >= 2 {
65        // 取最后两部分作为主域名
66        format!("{}.{}", parts[parts.len() - 2], parts[parts.len() - 1])
67    } else {
68        host.to_string()
69    };
70
71    // 构建 MQTT URL
72    format!("mqtts://mqtt.{}:{}", base_domain, DEFAULT_MQTT_PORT)
73}
74
75/// 获取完整的 API URL(包含路径前缀)
76fn get_full_api_url(base_url: &str) -> String {
77    // 如果已经包含 /api/v1,直接返回
78    if base_url.ends_with("/api/v1") {
79        return base_url.to_string();
80    }
81
82    // 否则添加路径前缀
83    format!("{}{}", base_url.trim_end_matches('/'), API_PATH_PREFIX)
84}
85
86// =============================================================================
87// SDK 客户端配置
88// =============================================================================
89
90/// SDK 客户端配置
91#[derive(Debug, Clone)]
92pub struct ClientConfig {
93    /// API 基础 URL(包含路径前缀)
94    pub api_url: String,
95    /// MQTT Broker URL
96    pub mqtt_broker_url: String,
97    /// JWT Token 或 API Key(可选)
98    pub token: Option<String>,
99    /// 用户 ID(可选,登录后设置)
100    pub user_id: Option<String>,
101}
102
103impl ClientConfig {
104    /// 创建新的配置
105    pub fn new(api_url: &str, mqtt_broker_url: &str) -> Self {
106        Self {
107            api_url: get_full_api_url(api_url),
108            mqtt_broker_url: mqtt_broker_url.to_string(),
109            token: None,
110            user_id: None,
111        }
112    }
113
114    /// 从环境变量创建配置
115    ///
116    /// # 环境变量
117    /// - `AGENTLINK_API_KEY`: API Key(必需)
118    /// - `AGENTLINK_API_URL`: API 基础 URL(可选,默认值:https://agentlink-api.feedecho.xyz)
119    ///   - 注意:只需要提供域名,不需要 /api/v1 路径
120    ///   - MQTT URL 会自动从 API URL 推导出来
121    ///
122    /// # Example
123    /// ```rust,ignore
124    /// // 设置环境变量
125    /// std::env::set_var("AGENTLINK_API_KEY", "your-api-key");
126    /// std::env::set_var("AGENTLINK_API_URL", "https://agentlink-api.feedecho.xyz");
127    ///
128    /// // 从环境变量创建配置
129    /// let config = ClientConfig::from_env().expect("Missing AGENTLINK_API_KEY");
130    /// // api_url = "https://agentlink-api.feedecho.xyz/api/v1"
131    /// // mqtt_url = "mqtts://mqtt.feedecho.xyz:8883"
132    /// ```
133    pub fn from_env() -> Result<Self, SdkError> {
134        let api_key = std::env::var(ENV_API_KEY)
135            .map_err(|_| SdkError::Config(format!(
136                "Missing environment variable: {}. Please set your API key.",
137                ENV_API_KEY
138            )))?;
139
140        let api_base_url = std::env::var(ENV_API_URL)
141            .unwrap_or_else(|_| DEFAULT_API_URL.to_string());
142
143        // 从 API URL 推导 MQTT URL
144        let mqtt_broker_url = derive_mqtt_url_from_api_url(&api_base_url);
145
146        // 获取完整的 API URL(包含路径前缀)
147        let api_url = get_full_api_url(&api_base_url);
148
149        Ok(Self {
150            api_url,
151            mqtt_broker_url,
152            token: Some(api_key),
153            user_id: None,
154        })
155    }
156
157    /// 尝试从环境变量创建配置,如果环境变量不存在则使用默认值
158    ///
159    /// # Example
160    /// ```rust,ignore
161    /// // 如果 AGENTLINK_API_KEY 不存在,返回 None
162    /// let config = ClientConfig::try_from_env();
163    ///
164    /// // 如果 AGENTLINK_API_KEY 存在,返回 Some(config)
165    /// std::env::set_var("AGENTLINK_API_KEY", "your-api-key");
166    /// let config = ClientConfig::try_from_env().expect("Should have config");
167    /// ```
168    pub fn try_from_env() -> Option<Self> {
169        Self::from_env().ok()
170    }
171
172    /// 设置 Token
173    pub fn with_token(mut self, token: String) -> Self {
174        self.token = Some(token);
175        self
176    }
177
178    /// 设置用户 ID
179    pub fn with_user_id(mut self, user_id: String) -> Self {
180        self.user_id = Some(user_id);
181        self
182    }
183}
184
185impl Default for ClientConfig {
186    fn default() -> Self {
187        Self {
188            api_url: get_full_api_url(DEFAULT_API_URL),
189            mqtt_broker_url: derive_mqtt_url_from_api_url(DEFAULT_API_URL),
190            token: None,
191            user_id: None,
192        }
193    }
194}
195
196/// AgentLink SDK 主客户端
197///
198/// 这是 SDK 的主要入口点,提供对所有服务的访问。
199///
200/// # Example
201///
202/// ```rust,ignore
203/// use agentlink_sdk::{AgentLinkClient, ClientConfig};
204///
205/// async fn example() {
206///     let config = ClientConfig::default();
207///     let client = AgentLinkClient::new(config);
208///
209///     // 登录
210///     let response = client.auth()
211///         .login_with_email_code("user@example.com", "123456")
212///         .await
213///         .unwrap();
214///
215///     // 连接 MQTT(自动启动事件循环)
216///     client.connect_mqtt(&response.token, &response.user.id)
217///         .await
218///         .unwrap();
219/// }
220/// ```
221///
222/// # 使用 API Key(类似 OpenAI SDK)
223///
224/// ```rust,ignore
225/// // 方式 1:从环境变量自动读取 API Key
226/// // 环境变量: AGENTLINK_API_KEY=your-api-key
227/// let client = AgentLinkClient::from_env().expect("Missing AGENTLINK_API_KEY");
228///
229/// // 方式 2:直接传入 API Key
230/// let client = AgentLinkClient::from_api_key("your-api-key");
231///
232/// // 连接 MQTT 并启动事件循环
233/// client.connect_and_start().await.expect("Connection failed");
234///
235/// // 注册事件处理器
236/// client.on(EVENT_MESSAGE_RECEIVED, |event| async move {
237///     println!("收到消息: {:?}", event);
238/// }).await;
239/// ```
240pub struct AgentLinkClient {
241    config: ClientConfig,
242    http: Arc<HttpClient>,
243    mqtt: Arc<RwLock<MqttClient>>,
244    event_loop: Arc<RwLock<EventLoop>>,
245    event_loop_handle: Arc<RwLock<Option<tokio::task::JoinHandle<()>>>>,
246}
247
248impl AgentLinkClient {
249    /// 创建新的 SDK 客户端
250    pub fn new(config: ClientConfig) -> Self {
251        let http = Arc::new(HttpClient::new(&config.api_url));
252        let mqtt = Arc::new(RwLock::new(MqttClient::new(&config.mqtt_broker_url)));
253        let (event_loop, _) = EventLoop::new();
254
255        // 如果配置中有 token,自动设置
256        if let Some(ref token) = config.token {
257            http.token_manager().set_token(token);
258        }
259
260        Self {
261            config,
262            http,
263            mqtt,
264            event_loop: Arc::new(RwLock::new(event_loop)),
265            event_loop_handle: Arc::new(RwLock::new(None)),
266        }
267    }
268
269    /// 从环境变量创建客户端(类似 OpenAI SDK 风格)
270    ///
271    /// # 环境变量
272    /// - `AGENTLINK_API_KEY`: API Key(必需)
273    /// - `AGENTLINK_API_URL`: API 基础 URL(可选)
274    /// - `AGENTLINK_MQTT_URL`: MQTT Broker URL(可选)
275    ///
276    /// # Example
277    /// ```rust,ignore
278    /// // 设置环境变量
279    /// std::env::set_var("AGENTLINK_API_KEY", "your-api-key");
280    ///
281    /// // 创建客户端
282    /// let client = AgentLinkClient::from_env().expect("Missing AGENTLINK_API_KEY");
283    /// ```
284    pub fn from_env() -> Result<Self, SdkError> {
285        let config = ClientConfig::from_env()?;
286        Ok(Self::new(config))
287    }
288
289    /// 尝试从环境变量创建客户端,如果环境变量不存在则返回 None
290    pub fn try_from_env() -> Option<Self> {
291        Self::from_env().ok()
292    }
293
294    /// 使用 API Key 创建客户端(类似 OpenAI SDK 风格)
295    ///
296    /// # Example
297    /// ```rust,ignore
298    /// let client = AgentLinkClient::from_api_key("your-api-key");
299    /// ```
300    pub fn from_api_key(api_key: &str) -> Self {
301        let config = ClientConfig::default().with_token(api_key.to_string());
302        Self::new(config)
303    }
304
305    /// 使用 API Key 和自定义 URL 创建客户端
306    ///
307    /// # Example
308    /// ```rust,ignore
309    /// let client = AgentLinkClient::from_api_key_with_urls(
310    ///     "your-api-key",
311    ///     "https://api.example.com/api/v1",
312    ///     "mqtts://mqtt.example.com:8883"
313    /// );
314    /// ```
315    pub fn from_api_key_with_urls(api_key: &str, api_url: &str, mqtt_url: &str) -> Self {
316        let config = ClientConfig::new(api_url, mqtt_url).with_token(api_key.to_string());
317        Self::new(config)
318    }
319
320    /// 获取当前配置
321    pub fn config(&self) -> &ClientConfig {
322        &self.config
323    }
324
325    /// 更新配置
326    pub fn update_config(&mut self, config: ClientConfig) {
327        self.config = config;
328    }
329
330    // ==================== 服务访问 ====================
331
332    /// 获取认证服务
333    ///
334    /// 不需要认证即可使用
335    pub fn auth(&self) -> AuthService {
336        AuthService::new(self.http.clone())
337    }
338
339    /// 获取用户服务
340    ///
341    /// 需要认证 Token 才能调用具体方法
342    pub fn users(&self) -> UserService {
343        UserService::new(self.http.clone())
344    }
345
346    /// 获取消息服务
347    ///
348    /// 需要认证 Token 才能调用具体方法
349    pub fn messages(&self) -> MessageService {
350        MessageService::with_mqtt(self.http.clone(), self.mqtt.clone())
351    }
352
353    /// 获取会话服务
354    ///
355    /// 需要认证 Token 才能调用具体方法
356    pub fn conversations(&self) -> ConversationService {
357        ConversationService::with_mqtt(self.http.clone(), self.mqtt.clone())
358    }
359
360    /// 获取好友服务
361    ///
362    /// 需要认证 Token 才能调用具体方法
363    pub fn friends(&self) -> FriendService {
364        FriendService::with_mqtt(self.http.clone(), self.mqtt.clone())
365    }
366
367    // ==================== MQTT 操作 ====================
368
369    /// 连接到 MQTT Broker
370    ///
371    /// # Arguments
372    /// * `token` - JWT Token
373    /// * `user_id` - 用户 ID
374    ///
375    /// # Returns
376    /// * `Ok(())` - 连接成功
377    /// * `Err(SdkError)` - 连接失败
378    pub async fn connect_mqtt(&self, token: &str, user_id: &str) -> SdkResult<()> {
379        let mqtt = self.mqtt.read().await;
380        mqtt.connect(token, user_id).await
381    }
382
383    /// 断开 MQTT 连接
384    pub async fn disconnect_mqtt(&self) -> SdkResult<()> {
385        let mqtt = self.mqtt.read().await;
386        mqtt.disconnect().await
387    }
388
389    /// 获取 MQTT 连接状态
390    pub async fn mqtt_connection_state(&self) -> MqttConnectionState {
391        let mqtt = self.mqtt.read().await;
392        mqtt.connection_state().await
393    }
394
395    /// 检查 MQTT 是否已连接
396    pub async fn is_mqtt_connected(&self) -> bool {
397        let mqtt = self.mqtt.read().await;
398        mqtt.is_connected().await
399    }
400
401    /// 订阅 MQTT 主题
402    ///
403    /// # Arguments
404    /// * `topic` - 主题路径
405    pub async fn subscribe(&self, topic: &str) -> SdkResult<()> {
406        let mqtt = self.mqtt.read().await;
407        mqtt.subscribe(topic).await
408    }
409
410    /// 取消订阅 MQTT 主题
411    ///
412    /// # Arguments
413    /// * `topic` - 主题路径
414    pub async fn unsubscribe(&self, topic: &str) -> SdkResult<()> {
415        let mqtt = self.mqtt.read().await;
416        mqtt.unsubscribe(topic).await
417    }
418
419    /// 发布 MQTT 消息
420    ///
421    /// # Arguments
422    /// * `topic` - 主题路径
423    /// * `payload` - 消息内容
424    pub async fn publish(&self, topic: &str, payload: &[u8]) -> SdkResult<()> {
425        let mqtt = self.mqtt.read().await;
426        mqtt.publish(topic, payload).await
427    }
428
429    // ==================== 事件注册(简化 API)====================
430
431    /// 注册事件回调
432    ///
433    /// # Example
434    /// ```rust,ignore
435    /// use agentlink_sdk::{AgentLinkClient, ClientConfig, EVENT_MESSAGE_RECEIVED, ServerEvent, MessageReceivedData};
436    ///
437    /// let client = AgentLinkClient::new(ClientConfig::default());
438    ///
439    /// client.on(EVENT_MESSAGE_RECEIVED, |event: ServerEvent<MessageReceivedData>| async move {
440    ///     println!("收到新消息: {:?}", event.data.content);
441    /// }).await;
442    /// ```
443    pub async fn on<T, F, Fut>(&self, event_type: &str, callback: F)
444    where
445        T: serde::de::DeserializeOwned + Send + Sync + 'static,
446        F: Fn(ServerEvent<T>) -> Fut + Send + Sync + 'static,
447        Fut: Future<Output = ()> + Send + 'static,
448    {
449        let event_loop = self.event_loop.read().await;
450        event_loop.on_event(event_type, callback).await;
451    }
452
453    /// 移除事件回调
454    ///
455    /// # Example
456    /// ```rust,ignore
457    /// client.off(EVENT_MESSAGE_RECEIVED).await;
458    /// ```
459    pub async fn off(&self, event_type: &str) {
460        let event_loop = self.event_loop.read().await;
461        event_loop.off_event(event_type).await;
462    }
463
464    /// 清除所有事件回调
465    ///
466    /// # Example
467    /// ```rust,ignore
468    /// client.clear_callbacks().await;
469    /// ```
470    pub async fn clear_callbacks(&self) {
471        let event_loop = self.event_loop.read().await;
472        event_loop.clear_callbacks().await;
473    }
474
475    // ==================== 事件循环(内部实现)====================
476
477    /// 获取事件循环的引用(高级用法)
478    ///
479    /// 大多数情况下,应该使用 `client.on()` 方法注册回调,
480    /// 而不是直接访问 event_loop。
481    pub fn event_loop(&self) -> Arc<RwLock<EventLoop>> {
482        self.event_loop.clone()
483    }
484
485    /// 启动事件循环
486    /// 
487    /// 这个方法会启动一个后台任务,持续处理 MQTT 事件并调用注册的回调函数。
488    /// 应该在 MQTT 连接成功后调用。
489    /// 
490    /// # Example
491    /// ```rust,ignore
492    /// // 先注册回调
493    /// client.event_loop().write().await.on_event("new_message", |event| Box::pin(async move {
494    ///     // 处理新消息
495    /// })).await;
496    /// 
497    /// // 连接 MQTT
498    /// client.connect_mqtt(&token, &user_id).await?;
499    /// 
500    /// // 启动事件循环
501    /// client.start_event_loop().await;
502    /// ```
503    pub async fn start_event_loop(&self) -> SdkResult<()> {
504        // 检查是否已经在运行
505        {
506            let event_loop = self.event_loop.read().await;
507            if event_loop.is_running().await {
508                tracing::warn!("[AgentLinkClient] Event loop already running");
509                return Ok(());
510            }
511        }
512        
513        // 获取 MQTT 事件接收器
514        let mqtt_rx = self.mqtt.read().await.take_event_receiver().await
515            .ok_or_else(|| crate::error::SdkError::Mqtt("Event receiver already taken".to_string()))?;
516        
517        // 创建事件循环通道
518        let (event_loop, event_rx) = EventLoop::new();
519        
520        // 更新 event_loop
521        {
522            let mut el = self.event_loop.write().await;
523            *el = event_loop;
524        }
525        
526        // 启动事件循环任务
527        let event_loop = self.event_loop.clone();
528        let handle = tokio::spawn(async move {
529            tracing::debug!("[AgentLinkClient] Event loop task started");
530            
531            // 启动事件循环处理
532            let event_loop = event_loop.read().await.clone();
533            event_loop.start(event_rx).await;
534            
535            tracing::debug!("[AgentLinkClient] Event loop task ended");
536        });
537        
538        // 启动 MQTT 事件转发任务
539        let event_sender = self.event_loop.read().await.event_sender();
540        tokio::spawn(async move {
541            let mut mqtt_rx = mqtt_rx;
542            loop {
543                match mqtt_rx.recv().await {
544                    Some(MqttEvent::MessageReceived { topic: _, payload }) => {
545                        // 尝试解析事件类型
546                        if let Ok(value) = serde_json::from_slice::<serde_json::Value>(&payload) {
547                            if let Some(event_type) = value.get("event_type").and_then(|v| v.as_str()) {
548                                let _ = event_sender.send((event_type.to_string(), value));
549                            }
550                        }
551                    }
552                    Some(MqttEvent::Connected) => {
553                        tracing::debug!("[AgentLinkClient] MQTT connected");
554                    }
555                    Some(MqttEvent::Disconnected) => {
556                        tracing::debug!("[AgentLinkClient] MQTT disconnected");
557                        break;
558                    }
559                    None => {
560                        tracing::debug!("[AgentLinkClient] MQTT event channel closed");
561                        break;
562                    }
563                    _ => {}
564                }
565            }
566        });
567        
568        *self.event_loop_handle.write().await = Some(handle);
569        
570        tracing::debug!("[AgentLinkClient] Event loop started");
571        Ok(())
572    }
573    
574    /// 停止事件循环
575    pub async fn stop_event_loop(&self) {
576        if let Some(handle) = self.event_loop_handle.write().await.take() {
577            handle.abort();
578            tracing::debug!("[AgentLinkClient] Event loop stopped");
579        }
580    }
581
582    /// 获取所有订阅的主题
583    pub async fn mqtt_subscriptions(&self) -> Vec<String> {
584        let mqtt = self.mqtt.read().await;
585        mqtt.subscriptions().await
586    }
587
588    // ==================== 便捷方法 ====================
589
590    /// 使用 API Key 连接 MQTT 并启动事件循环
591    ///
592    /// 这是一个便捷方法,适合使用 API Key 认证的场景(类似 OpenAI SDK)。
593    ///
594    /// # 流程
595    /// 1. 使用 API Key 作为 MQTT 密码连接
596    /// 2. 启动事件循环
597    ///
598    /// # Example
599    /// ```rust,ignore
600    /// // 从环境变量创建客户端
601    /// let client = AgentLinkClient::from_env().expect("Missing AGENTLINK_API_KEY");
602    ///
603    /// // 连接并启动事件循环
604    /// client.connect_and_start().await.expect("Connection failed");
605    ///
606    /// // 注册事件处理器
607    /// client.on(EVENT_MESSAGE_RECEIVED, |event| async move {
608    ///     println!("收到消息: {:?}", event);
609    /// }).await;
610    /// ```
611    pub async fn connect_and_start(&self) -> SdkResult<()> {
612        let api_key = self.get_token()
613            .ok_or_else(|| SdkError::Config("No API key set. Use from_env() or from_api_key()".to_string()))?;
614
615        // 使用 API Key 连接 MQTT(username 为 "api-key",password 为 API Key)
616        self.connect_mqtt_with_api_key(&api_key).await?;
617
618        // 启动事件循环
619        self.start_event_loop().await?;
620
621        tracing::info!("[AgentLinkClient] Connected and started with API key");
622        Ok(())
623    }
624
625    /// 使用 API Key 连接到 MQTT Broker
626    ///
627    /// API Key 作为 MQTT 密码,用户名为 "api-key"。
628    ///
629    /// # Arguments
630    /// * `api_key` - API Key
631    pub async fn connect_mqtt_with_api_key(&self, api_key: &str) -> SdkResult<()> {
632        let mqtt = self.mqtt.read().await;
633        mqtt.connect_with_api_key(api_key).await
634    }
635
636    /// 执行完整的登录流程
637    ///
638    /// 1. 登录获取 Token
639    /// 2. 连接 MQTT
640    /// 3. 启动事件循环
641    ///
642    /// # Arguments
643    /// * `email` - 邮箱地址
644    /// * `code` - 验证码
645    ///
646    /// # Returns
647    /// * `LoginResponse` - 包含 token 和用户信息
648    ///
649    /// # Note
650    /// Token 需要在使用此方法前通过 `with_token()` 或 `set_token()` 设置
651    pub async fn login(&self, email: &str, code: &str) -> SdkResult<crate::protocols::auth::LoginResponse> {
652        let response = self.auth().login_with_email_code(email, code).await?;
653
654        // 自动连接 MQTT
655        self.connect_mqtt(&response.token, &response.user.id).await?;
656
657        // 启动事件循环
658        if let Err(e) = self.start_event_loop().await {
659            tracing::warn!("[AgentLinkClient] Failed to start event loop: {}", e);
660        }
661
662        Ok(response)
663    }
664
665    /// 设置 Token(用于手动设置或更新)
666    ///
667    /// # Arguments
668    /// * `token` - JWT Token
669    pub fn set_token(&self, token: &str) {
670        self.http.token_manager().set_token(token);
671    }
672
673    /// 设置 Token 并返回自身(用于链式调用)
674    ///
675    /// # Example
676    /// ```rust,ignore
677    /// let client = AgentLinkClient::new(config)
678    ///     .with_token("jwt_token_here");
679    /// ```
680    ///
681    /// # Arguments
682    /// * `token` - JWT Token
683    pub fn with_token(&self, token: &str) -> &Self {
684        self.http.token_manager().set_token(token);
685        self
686    }
687
688    /// 清除 Token(用于登出)
689    pub fn clear_token(&self) {
690        self.http.token_manager().clear_token();
691    }
692
693    /// 获取当前 Token
694    pub fn get_token(&self) -> Option<String> {
695        self.http.token_manager().get_token()
696    }
697}
698
699#[cfg(test)]
700mod tests {
701    use super::*;
702
703    #[test]
704    fn test_derive_mqtt_url() {
705        // 标准域名
706        assert_eq!(
707            derive_mqtt_url_from_api_url("https://agentlink-api.feedecho.xyz"),
708            "mqtts://mqtt.feedecho.xyz:8883"
709        );
710
711        // 带端口的域名
712        assert_eq!(
713            derive_mqtt_url_from_api_url("http://localhost:9600"),
714            "mqtts://mqtt.localhost:8883"
715        );
716
717        // 带路径的 URL
718        assert_eq!(
719            derive_mqtt_url_from_api_url("https://api.example.com/api/v1"),
720            "mqtts://mqtt.example.com:8883"
721        );
722    }
723
724    #[test]
725    fn test_get_full_api_url() {
726        assert_eq!(
727            get_full_api_url("https://agentlink-api.feedecho.xyz"),
728            "https://agentlink-api.feedecho.xyz/api/v1"
729        );
730
731        // 已包含路径,不重复添加
732        assert_eq!(
733            get_full_api_url("https://agentlink-api.feedecho.xyz/api/v1"),
734            "https://agentlink-api.feedecho.xyz/api/v1"
735        );
736
737        // 带尾部斜杠
738        assert_eq!(
739            get_full_api_url("https://agentlink-api.feedecho.xyz/"),
740            "https://agentlink-api.feedecho.xyz/api/v1"
741        );
742    }
743
744    #[test]
745    fn test_client_config() {
746        let config = ClientConfig::new(
747            "http://localhost:8080",
748            "mqtts://localhost:8883",
749        );
750        assert_eq!(config.api_url, "http://localhost:8080/api/v1");
751        assert_eq!(config.mqtt_broker_url, "mqtts://localhost:8883");
752    }
753
754    #[test]
755    fn test_client_config_default() {
756        let config = ClientConfig::default();
757        assert_eq!(config.api_url, "https://agentlink-api.feedecho.xyz/api/v1");
758        assert_eq!(config.mqtt_broker_url, "mqtts://mqtt.feedecho.xyz:8883");
759        assert!(config.token.is_none());
760    }
761
762    #[test]
763    fn test_client_config_with_token() {
764        let config = ClientConfig::default()
765            .with_token("test-api-key".to_string());
766        assert_eq!(config.token, Some("test-api-key".to_string()));
767    }
768
769    #[test]
770    fn test_client_config_from_env_missing() {
771        // 清除环境变量
772        std::env::remove_var(ENV_API_KEY);
773
774        // 应该返回错误
775        let result = ClientConfig::from_env();
776        assert!(result.is_err());
777    }
778
779    #[test]
780    fn test_client_config_from_env_with_api_key() {
781        // 设置环境变量
782        std::env::set_var(ENV_API_KEY, "test-api-key-123");
783        std::env::remove_var(ENV_API_URL);
784
785        let config = ClientConfig::from_env().expect("Should have config");
786        assert_eq!(config.token, Some("test-api-key-123".to_string()));
787        // 使用默认 URL
788        assert_eq!(config.api_url, "https://agentlink-api.feedecho.xyz/api/v1");
789        // MQTT URL 从默认 API URL 推导
790        assert_eq!(config.mqtt_broker_url, "mqtts://mqtt.feedecho.xyz:8883");
791
792        // 清理
793        std::env::remove_var(ENV_API_KEY);
794    }
795
796    #[test]
797    fn test_client_config_from_env_with_custom_url() {
798        // 设置自定义 API URL
799        std::env::set_var(ENV_API_KEY, "test-api-key-456");
800        std::env::set_var(ENV_API_URL, "https://api.custom.com");
801
802        let config = ClientConfig::from_env().expect("Should have config");
803        assert_eq!(config.token, Some("test-api-key-456".to_string()));
804        // API URL 应该添加 /api/v1 路径
805        assert_eq!(config.api_url, "https://api.custom.com/api/v1");
806        // MQTT URL 从 API URL 推导
807        assert_eq!(config.mqtt_broker_url, "mqtts://mqtt.custom.com:8883");
808
809        // 清理
810        std::env::remove_var(ENV_API_KEY);
811        std::env::remove_var(ENV_API_URL);
812    }
813
814    #[tokio::test]
815    async fn test_client_new() {
816        let config = ClientConfig::default();
817        let client = AgentLinkClient::new(config);
818
819        assert!(!client.is_mqtt_connected().await);
820    }
821
822    #[tokio::test]
823    async fn test_client_new_with_token() {
824        let config = ClientConfig::default()
825            .with_token("test-api-key".to_string());
826        let client = AgentLinkClient::new(config);
827
828        assert_eq!(client.get_token(), Some("test-api-key".to_string()));
829    }
830
831    #[test]
832    fn test_client_from_api_key() {
833        let client = AgentLinkClient::from_api_key("my-api-key");
834        assert_eq!(client.get_token(), Some("my-api-key".to_string()));
835        assert_eq!(client.config().api_url, "https://agentlink-api.feedecho.xyz/api/v1");
836        assert_eq!(client.config().mqtt_broker_url, "mqtts://mqtt.feedecho.xyz:8883");
837    }
838
839    #[test]
840    fn test_client_from_api_key_with_urls() {
841        let client = AgentLinkClient::from_api_key_with_urls(
842            "my-api-key",
843            "https://custom.api.com/api/v1",
844            "mqtts://custom.mqtt.com:8883",
845        );
846        assert_eq!(client.get_token(), Some("my-api-key".to_string()));
847        assert_eq!(client.config().api_url, "https://custom.api.com/api/v1");
848        assert_eq!(client.config().mqtt_broker_url, "mqtts://custom.mqtt.com:8883");
849    }
850
851    #[test]
852    fn test_client_from_env() {
853        std::env::set_var(ENV_API_KEY, "env-api-key");
854        std::env::remove_var(ENV_API_URL);
855
856        let client = AgentLinkClient::from_env().expect("Should have client");
857        assert_eq!(client.get_token(), Some("env-api-key".to_string()));
858        assert_eq!(client.config().api_url, "https://agentlink-api.feedecho.xyz/api/v1");
859
860        // 清理
861        std::env::remove_var(ENV_API_KEY);
862    }
863}