Skip to main content

openlark_client/
lib.rs

1//! 🚀 OpenLark Client Library
2//!
3//! 现代化的飞书开放平台 Rust SDK,提供简洁、类型安全的 API 访问
4//! 集成 CoreError 企业级错误处理系统,提供全面的错误管理和恢复建议
5//!
6//! > 普通用户请优先使用根 crate `openlark`。
7//! >
8//! > `openlark-client` 保留为高级入口:适合只想复用统一客户端层,或明确需要直接控制客户端 feature 组合的场景。
9//!
10//! Canonical 公开入口约定:
11//!
12//! - 运行时入口优先使用 [`Client`] / [`ClientBuilder`]
13//! - 导入优先使用 `openlark_client::prelude::*`
14//! - 业务调用优先从 `client.<domain>` 字段链开始
15//!
16//! 普通用户请优先使用根 crate `openlark`;本 crate 保留为高级入口。
17//!
18//! ## 核心特性
19//!
20//! - **🎯 Feature-driven**: 基于编译时功能标志的模块化设计
21//! - **⚡ 零配置**: 支持从环境变量自动配置客户端
22//! - **🔒 类型安全**: 完全编译时验证的 API 调用
23//! - **🚀 异步优先**: 完全异步的客户端实现
24//! - **🏗️ 现代构建器**: 流畅的构建器模式 API
25//! - **🔍 能力诊断**: 编译期能力 catalog(字段唯一 / 禁用 feature 不产字段,trybuild 保证)
26//! - **🛡️ 企业级**: 基于 CoreError 的高级错误处理、重试和监控支持
27//! - **🌐 中文优先**: 100% 中文错误消息和文档,专为中国开发者优化
28//!
29//! ## 快速开始
30//!
31//! ### 基础用法
32//!
33//! ```rust,no_run
34//! use openlark_client::prelude::*;
35//!
36//! #[tokio::main]
37//! async fn main() -> Result<()> {
38//!     // 从环境变量创建客户端(推荐)
39//!     let client = Client::from_env()?;
40//!
41//!     // 单入口:meta 链式字段访问(需要对应 feature)
42//!     // - 通讯:client.communication.im...
43//!     // - 文档:client.docs.config()...
44//!     // - 认证:client.auth.app / client.auth.user / client.auth.oauth
45//!
46//!     Ok(())
47//! }
48//! ```
49//!
50//! ### 构建器模式
51//!
52//! ```rust,no_run
53//! use openlark_client::prelude::*;
54//! use std::time::Duration;
55//!
56//! fn main() -> Result<()> {
57//!     let _client = Client::builder()
58//!         .app_id("your_app_id")
59//!         .app_secret("your_app_secret")
60//!         .base_url("<https://open.feishu.cn>")
61//!         .timeout(Duration::from_secs(30))
62//!         .enable_log(true)
63//!         .build()?;
64//!     Ok(())
65//! }
66//! ```
67//!
68//! ### Endpoint 切换
69//!
70//! OpenLark 默认使用国内飞书 endpoint:<https://open.feishu.cn>。
71//! 如果你的应用运行在国际版 Lark,请将 `base_url` 切换为 <https://open.larksuite.com>。
72//!
73//! ```rust,no_run
74//! use openlark_client::prelude::*;
75//!
76//! fn main() -> Result<()> {
77//!     let _client = Client::builder()
78//!         .app_id("your_app_id")
79//!         .app_secret("your_app_secret")
80//!         .base_url("<https://open.larksuite.com>")
81//!         .build()?;
82//!     Ok(())
83//! }
84//! ```
85//!
86//! ### 环境变量配置
87//!
88//! 设置以下环境变量:
89//!
90//! ```bash
91//! export OPENLARK_APP_ID="your_app_id"
92//! export OPENLARK_APP_SECRET="your_app_secret"
93//! export OPENLARK_BASE_URL="<https://open.feishu.cn>"  # 可选,国际版请改为 <https://open.larksuite.com>
94//! export OPENLARK_TIMEOUT="30"  # 可选,秒
95//! export OPENLARK_ENABLE_LOG="true"  # 可选
96//! ```
97//!
98//! ## 功能标志
99//!
100//! 客户端使用 Rust 功能标志进行模块化编译:
101//!
102//! ```toml
103//! [dependencies]
104//! openlark-client = { version = "0.1", features = [
105//!     "communication",  # 通讯服务
106//!     "hr",           # 人力资源服务
107//!     "docs",         # 文档服务
108//!     "ai",           # AI 服务
109//!     "auth",         # 认证服务
110//!     "websocket",    # WebSocket 支持
111//! ]}
112//! ```
113//!
114//! ## 服务访问
115//!
116//! 每个启用功能都提供对应的 meta 链式入口(字段访问):
117//!
118//! ```rust,no_run
119//! use openlark_client::prelude::*;
120//!
121//! fn main() -> Result<()> {
122//! let client = Client::from_env()?;
123//!
124//! // 通讯入口(communication feature)
125//! #[cfg(feature = "communication")]
126//! let _comm = &client.communication;
127//!
128//! // 文档入口(docs feature)
129//! #[cfg(feature = "docs")]
130//! let _docs = &client.docs;
131//!
132//! // 认证入口(auth feature)
133//! #[cfg(feature = "auth")]
134//! let _auth = &client.auth;
135//! Ok(())
136//! }
137//! ```
138//!
139//! ## 高级用法
140//!
141//! ### 自定义配置
142//!
143//! ```rust,no_run
144//! use openlark_client::prelude::*;
145//! use std::time::Duration;
146//!
147//! fn main() -> Result<()> {
148//!     let _client = Client::builder()
149//!         .app_id("app_id")
150//!         .app_secret("app_secret")
151//!         .base_url("<https://open.feishu.cn>")
152//!         .timeout(Duration::from_secs(60))
153//!         .retry_count(3)
154//!         .enable_log(true)
155//!         .build()?;
156//!     Ok(())
157//! }
158//! ```
159//!
160//! ## 错误处理
161//!
162//! 客户端基于 CoreError 提供企业级错误处理,包含详细的错误分析、恢复建议和中文友好的错误消息:
163//!
164//! ```rust,no_run
165//! use openlark_client::prelude::*;
166//!
167//! match Client::from_env() {
168//!     Ok(client) => {
169//!         println!("客户端创建成功");
170//!         // 使用客户端...
171//!     },
172//!     Err(error) => {
173//!         // 用户友好的错误消息(中文)
174//!         eprintln!("❌ {}", error.user_message().unwrap_or("未知错误"));
175//!
176//!         // 获取错误恢复建议
177//!         eprintln!("💡 建议: {}", error.suggestion());
178//!
179//!         // 获取详细的恢复步骤
180//!         for (i, step) in error.recovery_steps().iter().enumerate() {
181//!             eprintln!("{}. {}", i + 1, step);
182//!         }
183//!
184//!         // 获取完整的错误分析报告
185//!         eprintln!("\n{}", ErrorAnalyzer::new(&error).detailed_report());
186//!
187//!         // 根据错误类型进行特定处理
188//!         if error.is_validation_error() {
189//!             eprintln!("请检查配置参数是否正确");
190//!         } else if error.is_network_error() {
191//!             eprintln!("请检查网络连接并稍后重试");
192//!         } else if error.is_auth_error() {
193//!             eprintln!("请检查应用凭据是否有效");
194//!         }
195//!     }
196//! }
197//! ```
198//!
199//! ### 错误类型和处理
200//!
201//! ```rust,no_run
202//! use openlark_client::prelude::*;
203//!
204//! // 捕获和处理特定类型的错误
205//! async fn send_message_with_error_handling() -> Result<()> {
206//!     let client = Client::from_env()?;
207//!
208//!     // 单入口:meta 链式字段访问。这里演示“拿到入口 + 挂上错误上下文”的模式。
209//!     #[cfg(feature = "communication")]
210//!     let _comm = &client.communication;
211//!
212//!     // 具体 API 调用请使用 openlark-communication 的强类型请求/构建器并在 `.await` 处处理 Result。
213//!     Ok(())
214//! }
215//! ```
216
217// 核心模块
218/// 编译期能力目录(Client 字段统一声明,#434–#437 / #471)
219pub(crate) mod capability;
220pub mod client;
221pub mod error;
222
223#[cfg(test)]
224mod test_utils;
225
226// meta.Project 维度的 API 调用链(数据源:api_list_export.csv)
227// CardKit 由 openlark-cardkit 提供链式调用;openlark-client 仅负责挂载到 Client 上。
228
229// WebSocket 模块(条件编译)
230/// WebSocket 客户端模块
231///
232/// 提供与飞书 WebSocket 服务的实时连接与事件接收。
233/// 公开入口:[`ws_client::LarkWsClient`]、[`ws_client::EventDispatcherHandler`]、
234/// [`ws_client::EventHandler`]。
235#[cfg(feature = "websocket")]
236pub mod ws_client;
237
238// ============================================================================
239// 核心类型重新导出
240// ============================================================================
241
242// 客户端和配置
243pub use client::{Client, ClientBuilder};
244
245// 企业级错误处理系统 - 基于 CoreError
246pub use error::{Error, Result};
247
248// 错误扩展功能
249pub use error::{
250    ClientErrorExt,         // 客户端错误扩展特征
251    ErrorAnalyzer,          // 错误分析器
252    with_context,           // 上下文错误处理
253    with_operation_context, // 操作上下文错误处理
254};
255
256// 错误创建便利函数
257pub use error::{
258    api_error,                 // API错误
259    authentication_error,      // 认证错误
260    business_error,            // 业务错误
261    configuration_error,       // 配置错误
262    internal_error,            // 内部错误
263    network_error,             // 网络错误
264    rate_limit_error,          // 限流错误
265    serialization_error,       // 序列化错误
266    service_unavailable_error, // 服务不可用错误
267    timeout_error,             // 超时错误
268    validation_error,          // 验证错误
269};
270
271// 注意:legacy_client 已在 v0.15.0 中移除
272// 请使用 `Client` 与 `ClientBuilder`
273// 迁移指南:https://github.com/foxzool/openlark/blob/main/docs/migration-guide.md
274
275// CardKit meta 调用链
276#[cfg(feature = "cardkit")]
277pub use openlark_cardkit::CardkitClient;
278
279// 顶层 meta client 类型保留在 `openlark-client` 作为高级入口;
280// 普通 SDK 使用者若只是接入业务能力,优先依赖根 crate `openlark`。
281#[cfg(feature = "auth")]
282pub use client::AuthClient;
283
284#[cfg(feature = "docs")]
285pub use openlark_docs::DocsClient;
286
287#[cfg(feature = "communication")]
288pub use openlark_communication::CommunicationClient;
289
290#[cfg(feature = "hr")]
291pub use openlark_hr::HrClient;
292
293#[cfg(feature = "meeting")]
294pub use openlark_meeting::MeetingClient;
295
296// 其他服务(当前未启用但已规划)
297//(历史上曾尝试在 openlark-client 内重复实现业务服务包装层,但现已收敛为 meta 单入口。)
298
299// 业务 crate 的 Client 类型导出(统一从源 crate re-export)
300#[cfg(feature = "ai")]
301pub use openlark_ai::AiClient;
302
303#[cfg(feature = "workflow")]
304pub use openlark_workflow::WorkflowClient;
305
306#[cfg(feature = "platform")]
307pub use openlark_platform::PlatformClient;
308
309#[cfg(feature = "application")]
310pub use openlark_application::ApplicationClient;
311
312#[cfg(feature = "helpdesk")]
313pub use openlark_helpdesk::HelpdeskClient;
314
315#[cfg(feature = "mail")]
316pub use openlark_mail::MailClient;
317
318#[cfg(feature = "bot")]
319pub use openlark_bot::BotClient;
320
321#[cfg(feature = "analytics")]
322pub use openlark_analytics::AnalyticsClient;
323
324#[cfg(feature = "user")]
325pub use openlark_user::UserClient;
326
327#[cfg(feature = "security")]
328pub use openlark_security::SecurityClient;
329
330// ============================================================================
331// Core 系统类型重新导出
332// ============================================================================
333
334// 重新导出 openlark-core 核心类型
335pub use openlark_core::{SDKResult as CoreResult, config::Config as CoreConfig};
336
337// 错误系统核心类型
338pub use openlark_core::error::{CoreError, ErrorCode, ErrorSeverity, ErrorTrait, ErrorType};
339
340// ============================================================================
341// 类型别名和便利定义
342// ============================================================================
343
344/// 🚨 SDK 结果类型别名(与 Core 系统兼容)
345pub type SDKResult<T> = openlark_core::SDKResult<T>;
346
347/// 🚀 预导出模块 - 包含最常用的类型和特征
348///
349/// 使用预导出可以简化导入,提供一站式类型访问:
350///
351/// ```rust,no_run
352/// use openlark_client::prelude::*;
353///
354/// fn main() -> Result<()> {
355///     let client = Client::from_env()?;
356///     #[cfg(feature = "docs")]
357///     let _docs = &client.docs;
358///     Ok(())
359/// }
360/// ```
361///
362/// 说明:
363/// - `openlark_client::prelude` 面向直接依赖客户端层的高级调用方
364/// - 根 crate `open_lark::prelude` 则保持更小、更稳定的入口面
365pub mod prelude {
366    // ============================================================================
367    // 核心客户端类型
368    // ============================================================================
369
370    // 客户端和配置
371    pub use crate::{Client, ClientBuilder};
372
373    // 企业级错误处理系统
374    pub use crate::{Error, Result};
375
376    // ============================================================================
377    // 错误处理扩展
378    // ============================================================================
379
380    // 错误扩展特征和分析器
381    pub use crate::{
382        ClientErrorExt,         // 客户端错误扩展特征
383        ErrorAnalyzer,          // 错误分析器
384        with_context,           // 上下文错误处理
385        with_operation_context, // 操作上下文错误处理
386    };
387
388    // 错误创建便利函数
389    pub use crate::{
390        api_error,                 // API错误
391        authentication_error,      // 认证错误
392        business_error,            // 业务错误
393        configuration_error,       // 配置错误
394        internal_error,            // 内部错误
395        network_error,             // 网络错误
396        rate_limit_error,          // 限流错误
397        serialization_error,       // 序列化错误
398        service_unavailable_error, // 服务不可用错误
399        timeout_error,             // 超时错误
400        validation_error,          // 验证错误
401    };
402
403    // Core 错误系统类型
404    pub use openlark_core::error::{CoreError, ErrorCode, ErrorSeverity, ErrorTrait, ErrorType};
405
406    // meta 风格链式入口(字段链式)
407    #[cfg(feature = "cardkit")]
408    pub use openlark_cardkit::CardkitClient;
409
410    #[cfg(feature = "auth")]
411    pub use crate::AuthClient;
412
413    #[cfg(feature = "docs")]
414    pub use openlark_docs::DocsClient;
415
416    #[cfg(feature = "communication")]
417    pub use openlark_communication::CommunicationClient;
418
419    #[cfg(feature = "hr")]
420    pub use openlark_hr::HrClient;
421
422    #[cfg(feature = "meeting")]
423    pub use openlark_meeting::MeetingClient;
424
425    // 其他服务(当前未启用但已规划)
426    //(历史上曾尝试在 openlark-client 内重复实现业务服务包装层,但现已收敛为 meta 单入口。)
427
428    #[cfg(feature = "ai")]
429    pub use openlark_ai::AiClient;
430
431    #[cfg(feature = "workflow")]
432    pub use crate::WorkflowClient;
433
434    #[cfg(feature = "platform")]
435    pub use crate::PlatformClient;
436
437    #[cfg(feature = "application")]
438    pub use crate::ApplicationClient;
439
440    #[cfg(feature = "helpdesk")]
441    pub use crate::HelpdeskClient;
442
443    #[cfg(feature = "mail")]
444    pub use crate::MailClient;
445
446    #[cfg(feature = "bot")]
447    pub use crate::BotClient;
448
449    #[cfg(feature = "analytics")]
450    pub use crate::AnalyticsClient;
451
452    #[cfg(feature = "user")]
453    pub use crate::UserClient;
454
455    #[cfg(feature = "security")]
456    pub use crate::SecurityClient;
457
458    // ============================================================================
459    // 便利类型别名
460    // ============================================================================
461    //(历史上曾尝试在 openlark-client 内重复实现业务服务包装层,但现已收敛为 meta 单入口。)
462
463    // ============================================================================
464    // 便利类型别名
465    // ============================================================================
466
467    /// 🚨 SDK 结果类型别名(与 Core 系统兼容)
468    pub type SDKResult<T> = openlark_core::SDKResult<T>;
469
470    // ============================================================================
471    // 常用宏和便利导入
472    // ============================================================================
473
474    pub use openlark_core::{SDKResult as CoreResult, config::Config as CoreConfig};
475}
476
477/// 🏷️ 库信息
478pub mod info {
479    /// 库名称
480    pub const NAME: &str = "OpenLark Client";
481    /// 库版本
482    pub const VERSION: &str = env!("CARGO_PKG_VERSION");
483    /// 库描述
484    pub const DESCRIPTION: &str = env!("CARGO_PKG_DESCRIPTION");
485    /// 仓库地址
486    pub const REPOSITORY: &str = env!("CARGO_PKG_REPOSITORY");
487}
488
489/// 🔧 实用工具函数
490pub mod utils;
491
492#[cfg(test)]
493#[allow(unused_imports)]
494mod tests {
495    use super::*;
496
497    #[test]
498    fn test_library_info() {
499        assert_ne!(info::NAME, "");
500        assert_ne!(info::VERSION, "");
501        assert_ne!(info::DESCRIPTION, "");
502    }
503
504    #[test]
505    fn test_enabled_features() {
506        let features = utils::get_enabled_features();
507        // auth 功能始终启用
508        assert!(features.contains(&"auth"));
509    }
510
511    #[test]
512    fn test_prelude_reexports() {
513        // 确保 prelude 模块正确导出了核心类型
514        use prelude::*;
515
516        // 这些导入应该能够工作
517        let _builder: ClientBuilder = ClientBuilder::new();
518
519        // 测试配置创建
520        let _config = CoreConfig::builder()
521            .app_id("test")
522            .app_secret("test")
523            .build();
524    }
525
526    #[test]
527    fn test_check_env_config_success() {
528        test_utils::with_env_vars(
529            &[
530                ("OPENLARK_APP_ID", Some("test_app_id")),
531                ("OPENLARK_APP_SECRET", Some("test_secret")),
532            ],
533            || {
534                let result = utils::check_env_config();
535                assert!(result.is_ok());
536            },
537        );
538    }
539
540    #[test]
541    fn test_check_env_config_missing_app_id() {
542        test_utils::with_env_vars(
543            &[
544                ("OPENLARK_APP_ID", None),
545                ("OPENLARK_APP_SECRET", Some("test_secret")),
546            ],
547            || {
548                let result = utils::check_env_config();
549                assert!(result.is_err());
550            },
551        );
552    }
553
554    #[test]
555    fn test_check_env_config_empty_app_id() {
556        test_utils::with_env_vars(
557            &[
558                ("OPENLARK_APP_ID", Some("")),
559                ("OPENLARK_APP_SECRET", Some("test_secret")),
560            ],
561            || {
562                let result = utils::check_env_config();
563                assert!(result.is_err());
564            },
565        );
566    }
567
568    #[test]
569    fn test_check_env_config_missing_app_secret() {
570        test_utils::with_env_vars(
571            &[
572                ("OPENLARK_APP_ID", Some("test_app_id")),
573                ("OPENLARK_APP_SECRET", None),
574            ],
575            || {
576                let result = utils::check_env_config();
577                assert!(result.is_err());
578            },
579        );
580    }
581
582    #[test]
583    fn test_check_env_config_empty_app_secret() {
584        test_utils::with_env_vars(
585            &[
586                ("OPENLARK_APP_ID", Some("test_app_id")),
587                ("OPENLARK_APP_SECRET", Some("")),
588            ],
589            || {
590                let result = utils::check_env_config();
591                assert!(result.is_err());
592            },
593        );
594    }
595
596    #[test]
597    fn test_check_env_config_invalid_base_url() {
598        test_utils::with_env_vars(
599            &[
600                ("OPENLARK_APP_ID", Some("test_app_id")),
601                ("OPENLARK_APP_SECRET", Some("test_secret")),
602                ("OPENLARK_BASE_URL", Some("invalid_url")),
603            ],
604            || {
605                let result = utils::check_env_config();
606                assert!(result.is_err());
607            },
608        );
609    }
610
611    #[test]
612    fn test_check_env_config_valid_base_url() {
613        test_utils::with_env_vars(
614            &[
615                ("OPENLARK_APP_ID", Some("test_app_id")),
616                ("OPENLARK_APP_SECRET", Some("test_secret")),
617                ("OPENLARK_BASE_URL", Some("https://open.feishu.cn")),
618            ],
619            || {
620                let result = utils::check_env_config();
621                assert!(result.is_ok());
622            },
623        );
624    }
625
626    #[test]
627    fn test_check_env_config_invalid_timeout() {
628        test_utils::with_env_vars(
629            &[
630                ("OPENLARK_APP_ID", Some("test_app_id")),
631                ("OPENLARK_APP_SECRET", Some("test_secret")),
632                ("OPENLARK_TIMEOUT", Some("not_a_number")),
633            ],
634            || {
635                let result = utils::check_env_config();
636                assert!(result.is_err());
637            },
638        );
639    }
640
641    #[test]
642    fn test_check_env_config_valid_timeout() {
643        test_utils::with_env_vars(
644            &[
645                ("OPENLARK_APP_ID", Some("test_app_id")),
646                ("OPENLARK_APP_SECRET", Some("test_secret")),
647                ("OPENLARK_TIMEOUT", Some("30")),
648            ],
649            || {
650                let result = utils::check_env_config();
651                assert!(result.is_ok());
652            },
653        );
654    }
655
656    #[test]
657    fn test_create_config_from_env_success() {
658        test_utils::with_env_vars(
659            &[
660                ("OPENLARK_APP_ID", Some("test_app_id")),
661                ("OPENLARK_APP_SECRET", Some("test_secret")),
662                ("OPENLARK_BASE_URL", Some("https://open.feishu.cn")),
663            ],
664            || {
665                let result = utils::create_config_from_env();
666                assert!(result.is_ok());
667                let config = result.unwrap();
668                assert_eq!(config.app_id(), "test_app_id");
669                assert_eq!(config.app_secret(), "test_secret");
670            },
671        );
672    }
673
674    #[test]
675    fn test_create_config_from_env_uses_canonical_env_interpretation() {
676        // 委托 Config::from_env:ENABLE_LOG 用 parse_env_bool("0"→false),
677        // 缺省 enable_log 保持 core 默认 true(不再手写默认 false)。
678        test_utils::with_env_vars(
679            &[
680                ("OPENLARK_APP_ID", Some("test_app_id")),
681                ("OPENLARK_APP_SECRET", Some("test_secret")),
682                ("OPENLARK_TIMEOUT", Some("45")),
683                ("OPENLARK_ENABLE_LOG", Some("0")),
684                ("OPENLARK_RETRY_COUNT", Some("5")),
685            ],
686            || {
687                let config = utils::create_config_from_env().unwrap();
688                assert_eq!(
689                    config.req_timeout(),
690                    Some(std::time::Duration::from_secs(45))
691                );
692                assert!(!config.enable_log());
693                assert_eq!(config.retry_count(), 5);
694            },
695        );
696
697        test_utils::with_env_vars(
698            &[
699                ("OPENLARK_APP_ID", Some("test_app_id")),
700                ("OPENLARK_APP_SECRET", Some("test_secret")),
701                ("OPENLARK_ENABLE_LOG", None),
702            ],
703            || {
704                let config = utils::create_config_from_env().unwrap();
705                assert!(
706                    config.enable_log(),
707                    "未设 OPENLARK_ENABLE_LOG 时应为 core 默认 true"
708                );
709            },
710        );
711    }
712
713    #[test]
714    fn test_create_config_from_env_missing_vars() {
715        test_utils::with_env_vars(
716            &[("OPENLARK_APP_ID", None), ("OPENLARK_APP_SECRET", None)],
717            || {
718                let result = utils::create_config_from_env();
719                assert!(result.is_err());
720            },
721        );
722    }
723
724    #[test]
725    fn test_get_config_summary() {
726        let config = openlark_core::config::Config::builder()
727            .app_id("test_app_id")
728            .app_secret("test_secret_key")
729            .base_url("https://open.feishu.cn")
730            .req_timeout(std::time::Duration::from_secs(30))
731            .build();
732
733        let summary = utils::get_config_summary(&config);
734        assert_eq!(summary.app_id, "test_app_id");
735        assert!(summary.app_secret_set);
736        assert_eq!(summary.base_url, "https://open.feishu.cn");
737        assert_eq!(
738            summary.req_timeout,
739            Some(std::time::Duration::from_secs(30))
740        );
741    }
742
743    #[test]
744    fn test_config_summary_friendly_description() {
745        let summary = openlark_core::config::ConfigSummary {
746            app_id: "test_app".to_string(),
747            app_secret_set: true,
748            app_type: openlark_core::constants::AppType::SelfBuild,
749            enable_token_cache: true,
750            base_url: "https://open.feishu.cn".to_string(),
751            allow_custom_base_url: false,
752            req_timeout: Some(std::time::Duration::from_secs(30)),
753            retry_count: 3,
754            enable_log: false,
755            header_count: 0,
756            max_response_size: 100 * 1024 * 1024,
757        };
758
759        let description = summary.friendly_description();
760        assert!(description.contains("test_app"));
761        assert!(description.contains("open.feishu.cn"));
762        assert!(description.contains("30s"));
763    }
764
765    #[test]
766    fn test_config_summary_friendly_description_no_timeout() {
767        let summary = openlark_core::config::ConfigSummary {
768            app_id: "test_app".to_string(),
769            app_secret_set: true,
770            app_type: openlark_core::constants::AppType::SelfBuild,
771            enable_token_cache: true,
772            base_url: "https://open.feishu.cn".to_string(),
773            allow_custom_base_url: false,
774            req_timeout: None,
775            retry_count: 3,
776            enable_log: false,
777            header_count: 0,
778            max_response_size: 100 * 1024 * 1024,
779        };
780
781        let description = summary.friendly_description();
782        assert!(description.contains("test_app"));
783        assert!(description.contains("None"));
784    }
785
786    #[test]
787    fn test_validate_feature_dependencies_success() {
788        // auth 始终启用,应该没有依赖问题
789        let result = utils::validate_feature_dependencies();
790        assert!(result.is_ok());
791    }
792
793    #[test]
794    fn test_diagnose_system_success() {
795        test_utils::with_env_vars(
796            &[
797                ("OPENLARK_APP_ID", Some("test_app_id")),
798                ("OPENLARK_APP_SECRET", Some("test_secret")),
799            ],
800            || {
801                let diagnostics = utils::diagnose_system();
802                assert!(
803                    diagnostics.env_config_status.contains("✅")
804                        || diagnostics.env_config_status.contains("❌")
805                );
806                assert!(diagnostics.feature_deps_status.contains("✅"));
807                assert!(!diagnostics.enabled_features.is_empty());
808            },
809        );
810    }
811
812    #[test]
813    fn test_system_diagnostics_new() {
814        let diagnostics = utils::SystemDiagnostics::new();
815        assert_eq!(diagnostics.env_config_status, "未检查");
816        assert_eq!(diagnostics.feature_deps_status, "未检查");
817        assert!(diagnostics.enabled_features.is_empty());
818        assert!(diagnostics.issues.is_empty());
819    }
820
821    #[test]
822    fn test_system_diagnostics_add_issue() {
823        let mut diagnostics = utils::SystemDiagnostics::new();
824        diagnostics.add_issue("测试类别", "测试描述");
825        assert_eq!(diagnostics.issues.len(), 1);
826        assert_eq!(diagnostics.issues[0].category, "测试类别");
827        assert_eq!(diagnostics.issues[0].description, "测试描述");
828    }
829
830    #[test]
831    fn test_system_diagnostics_health_summary_healthy() {
832        let diagnostics = utils::SystemDiagnostics::new();
833        let summary = diagnostics.health_summary();
834        assert!(summary.contains("🟢"));
835        assert!(summary.contains("健康"));
836    }
837
838    #[test]
839    fn test_system_diagnostics_health_summary_with_issues() {
840        let mut diagnostics = utils::SystemDiagnostics::new();
841        diagnostics.add_issue("测试类别", "测试描述");
842        let summary = diagnostics.health_summary();
843        assert!(summary.contains("🟡"));
844        assert!(summary.contains("1"));
845    }
846
847    #[test]
848    fn test_system_diagnostics_has_critical_issues_true() {
849        let mut diagnostics = utils::SystemDiagnostics::new();
850        diagnostics.add_issue("环境变量", "配置错误");
851        assert!(diagnostics.has_critical_issues());
852    }
853
854    #[test]
855    fn test_system_diagnostics_has_critical_issues_false() {
856        let mut diagnostics = utils::SystemDiagnostics::new();
857        diagnostics.add_issue("其他问题", "一般错误");
858        assert!(!diagnostics.has_critical_issues());
859    }
860
861    #[test]
862    fn test_system_diagnostics_default() {
863        let diagnostics: utils::SystemDiagnostics = Default::default();
864        assert_eq!(diagnostics.env_config_status, "未检查");
865    }
866
867    #[test]
868    fn test_diagnostic_issue_clone() {
869        let issue = utils::DiagnosticIssue {
870            category: "测试".to_string(),
871            description: "描述".to_string(),
872        };
873        let cloned = issue.clone();
874        assert_eq!(cloned.category, "测试");
875        assert_eq!(cloned.description, "描述");
876    }
877}