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