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