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}