vkteams_bot_cli/commands/
diagnostic.rs

1//! Diagnostic commands module
2//!
3//! This module contains all commands related to diagnostics, testing, and system information.
4
5use crate::commands::{Command, OutputFormat};
6use crate::config::Config;
7use crate::constants::ui::emoji;
8use crate::errors::prelude::{CliError, Result as CliResult};
9use crate::file_utils;
10use crate::output::{CliResponse, OutputFormatter};
11use crate::utils::output::print_success_result;
12use crate::utils::{validate_directory_path, validate_file_id};
13use async_trait::async_trait;
14use clap::{Subcommand, ValueHint};
15use colored::Colorize;
16use serde_json::json;
17use tracing::{debug, info};
18use vkteams_bot::prelude::*;
19
20/// All diagnostic-related commands
21#[derive(Subcommand, Debug, Clone)]
22pub enum DiagnosticCommands {
23    /// Get bot information and status
24    GetSelf {
25        /// Show detailed bot information
26        #[arg(short, long)]
27        detailed: bool,
28    },
29    /// Get events once or listen with optional flag
30    GetEvents {
31        #[arg(short, long, required = false, value_name = "LISTEN")]
32        listen: Option<bool>,
33    },
34    /// Download file with given ID into specified path
35    GetFile {
36        #[arg(short = 'f', long, required = true, value_name = "FILE_ID")]
37        file_id: String,
38        #[arg(short = 'p', long, required = false, value_name = "FILE_PATH", value_hint = ValueHint::DirPath)]
39        file_path: String,
40    },
41    /// Perform comprehensive health check
42    HealthCheck,
43    /// Test network connectivity to API endpoints
44    NetworkTest,
45    /// Show system and environment information
46    SystemInfo,
47    /// Test API rate limits
48    RateLimitTest {
49        /// Number of requests to send
50        #[arg(short = 'n', long, default_value = "10")]
51        requests: u32,
52        /// Delay between requests in milliseconds
53        #[arg(short = 'd', long, default_value = "100")]
54        delay_ms: u64,
55    },
56}
57
58#[async_trait]
59impl Command for DiagnosticCommands {
60    async fn execute(&self, bot: &Bot) -> CliResult<()> {
61        match self {
62            DiagnosticCommands::GetSelf { detailed } => execute_get_self(bot, *detailed).await,
63            DiagnosticCommands::GetEvents { listen } => {
64                execute_get_events(bot, listen.unwrap_or(false)).await
65            }
66            DiagnosticCommands::GetFile { file_id, file_path } => {
67                execute_get_file(bot, file_id, file_path).await
68            }
69            DiagnosticCommands::HealthCheck => execute_health_check(bot).await,
70            DiagnosticCommands::NetworkTest => execute_network_test(bot).await,
71            DiagnosticCommands::SystemInfo => execute_system_info().await,
72            DiagnosticCommands::RateLimitTest { requests, delay_ms } => {
73                execute_rate_limit_test(bot, *requests, *delay_ms).await
74            }
75        }
76    }
77
78    fn name(&self) -> &'static str {
79        match self {
80            DiagnosticCommands::GetSelf { .. } => "get-self",
81            DiagnosticCommands::GetEvents { .. } => "get-events",
82            DiagnosticCommands::GetFile { .. } => "get-file",
83            DiagnosticCommands::HealthCheck => "health-check",
84            DiagnosticCommands::NetworkTest => "network-test",
85            DiagnosticCommands::SystemInfo => "system-info",
86            DiagnosticCommands::RateLimitTest { .. } => "rate-limit-test",
87        }
88    }
89
90    fn validate(&self) -> CliResult<()> {
91        match self {
92            DiagnosticCommands::GetFile { file_id, file_path } => {
93                validate_file_id(file_id)?;
94                if !file_path.is_empty() {
95                    validate_directory_path(file_path)?;
96                }
97            }
98            DiagnosticCommands::RateLimitTest {
99                requests,
100                delay_ms: _,
101            } => {
102                if *requests == 0 || *requests > 1000 {
103                    return Err(CliError::InputError(
104                        "Number of requests must be between 1 and 1000".to_string(),
105                    ));
106                }
107            }
108            _ => {} // Other commands don't need validation
109        }
110        Ok(())
111    }
112
113    /// New method for structured output support
114    async fn execute_with_output(&self, bot: &Bot, output_format: &OutputFormat) -> CliResult<()> {
115        let response = match self {
116            DiagnosticCommands::GetSelf { detailed } => {
117                execute_get_self_structured(bot, *detailed).await
118            }
119            DiagnosticCommands::GetEvents { listen } => {
120                execute_get_events_structured(bot, listen.unwrap_or(false)).await
121            }
122            DiagnosticCommands::GetFile { file_id, file_path } => {
123                execute_get_file_structured(bot, file_id, file_path).await
124            }
125            DiagnosticCommands::HealthCheck => execute_health_check_structured(bot).await,
126            DiagnosticCommands::NetworkTest => execute_network_test_structured(bot).await,
127            DiagnosticCommands::SystemInfo => execute_system_info_structured().await,
128            DiagnosticCommands::RateLimitTest { requests, delay_ms } => {
129                execute_rate_limit_test_structured(bot, *requests, *delay_ms).await
130            }
131        };
132
133        OutputFormatter::print(&response, output_format)?;
134
135        if !response.success {
136            return Err(CliError::UnexpectedError("Command failed".to_string()));
137        }
138
139        Ok(())
140    }
141}
142
143// Command execution functions
144
145async fn execute_get_self(bot: &Bot, detailed: bool) -> CliResult<()> {
146    debug!("Getting bot information");
147
148    let request = RequestSelfGet::new(());
149    let result = bot
150        .send_api_request(request)
151        .await
152        .map_err(CliError::ApiError)?;
153
154    if detailed {
155        info!("Bot information retrieved successfully");
156        print_success_result(&result, &OutputFormat::Pretty)?;
157    } else {
158        // Show simplified bot info
159        println!("{} Bot is configured and accessible", emoji::CHECK);
160        if let Ok(json_str) = serde_json::to_string_pretty(&result) {
161            println!("{}", json_str.green());
162        }
163    }
164
165    Ok(())
166}
167
168async fn execute_get_events(bot: &Bot, listen: bool) -> CliResult<()> {
169    debug!("Getting events, listen mode: {}", listen);
170
171    if listen {
172        info!("Starting event listener (long polling)...");
173        println!(
174            "{} Starting event listener. Press Ctrl+C to stop.",
175            emoji::ROCKET
176        );
177
178        match bot.event_listener(handle_event).await {
179            Ok(()) => (),
180            Err(e) => return Err(CliError::ApiError(e)),
181        }
182    } else {
183        let result = bot
184            .send_api_request(RequestEventsGet::new(bot.get_last_event_id()).with_poll_time(30))
185            .await
186            .map_err(CliError::ApiError)?;
187
188        info!("Successfully retrieved events");
189        print_success_result(&result, &OutputFormat::Pretty)?;
190    }
191
192    Ok(())
193}
194
195async fn execute_get_file(bot: &Bot, file_id: &str, file_path: &str) -> CliResult<()> {
196    debug!("Downloading file {} to {}", file_id, file_path);
197
198    let downloaded_path = file_utils::download_and_save_file(bot, file_id, file_path).await?;
199
200    info!("Successfully downloaded file with ID: {}", file_id);
201    println!(
202        "{} File downloaded to: {}",
203        emoji::CHECK,
204        downloaded_path.display().to_string().green()
205    );
206
207    Ok(())
208}
209
210async fn execute_health_check(bot: &Bot) -> CliResult<()> {
211    println!(
212        "{} Performing comprehensive health check...",
213        emoji::TEST_TUBE.bold().blue()
214    );
215    println!();
216
217    let mut all_passed = true;
218
219    // Test 1: Basic connectivity
220    print!("{} Testing basic API connectivity... ", emoji::GEAR);
221    match bot.send_api_request(RequestSelfGet::new(())).await {
222        Ok(_) => println!("{}", "PASS".green()),
223        Err(e) => {
224            println!("{} - {}", "FAIL".red(), e);
225            all_passed = false;
226        }
227    }
228
229    // Test 2: Configuration check
230    print!("{} Checking configuration... ", emoji::GEAR);
231    match Config::from_file() {
232        Ok(config) => {
233            if config.api.token.is_some() && config.api.url.is_some() {
234                println!("{}", "PASS".green());
235            } else {
236                println!("{} - Missing required configuration", "FAIL".red());
237                all_passed = false;
238            }
239        }
240        Err(_) => {
241            println!("{} - Configuration file not found", "FAIL".red());
242            all_passed = false;
243        }
244    }
245
246    // Test 3: Network latency
247    print!("{} Testing network latency... ", emoji::GEAR);
248    let start = std::time::Instant::now();
249    match bot.send_api_request(RequestSelfGet::new(())).await {
250        Ok(_) => {
251            let latency = start.elapsed();
252            if latency.as_millis() < 1000 {
253                println!("{} - {}ms", "PASS".green(), latency.as_millis());
254            } else {
255                println!(
256                    "{} - High latency: {}ms",
257                    "WARN".yellow(),
258                    latency.as_millis()
259                );
260            }
261        }
262        Err(e) => {
263            println!("{} - {}", "FAIL".red(), e);
264            all_passed = false;
265        }
266    }
267
268    println!();
269    if all_passed {
270        println!("{} All health checks passed!", emoji::CHECK.bold().green());
271    } else {
272        println!(
273            "{} Some health checks failed. Check configuration and network connectivity.",
274            emoji::WARNING.bold().yellow()
275        );
276    }
277
278    Ok(())
279}
280
281async fn execute_network_test(bot: &Bot) -> CliResult<()> {
282    println!(
283        "{} Testing network connectivity...",
284        emoji::GEAR.bold().blue()
285    );
286    println!();
287
288    // Test multiple endpoints with timing
289    let endpoints = vec![("Bot Info", RequestSelfGet::new(()))];
290
291    for (name, request) in endpoints {
292        print!("Testing {name}: ");
293        let start = std::time::Instant::now();
294
295        match bot.send_api_request(request).await {
296            Ok(_) => {
297                let duration = start.elapsed();
298                println!("{} ({}ms)", "OK".green(), duration.as_millis());
299            }
300            Err(e) => {
301                println!("{} - {}", "FAILED".red(), e);
302            }
303        }
304    }
305
306    println!();
307    println!("{} Network test completed", emoji::CHECK);
308
309    Ok(())
310}
311
312async fn execute_system_info() -> CliResult<()> {
313    println!("{} System Information", emoji::INFO.bold().blue());
314    println!();
315
316    // Runtime information
317    println!("{}", "Runtime:".bold().green());
318    println!("  OS: {}", std::env::consts::OS);
319    println!("  Architecture: {}", std::env::consts::ARCH);
320    println!("  Family: {}", std::env::consts::FAMILY);
321
322    // Current directory
323    if let Ok(current_dir) = std::env::current_dir() {
324        println!("  Current directory: {}", current_dir.display());
325    }
326
327    // Environment variables
328    println!("\n{}", "Environment:".bold().green());
329    let env_vars = [
330        "VKTEAMS_BOT_API_TOKEN",
331        "VKTEAMS_BOT_API_URL",
332        "VKTEAMS_PROXY",
333        "VKTEAMS_LOG_LEVEL",
334    ];
335
336    for var in &env_vars {
337        match std::env::var(var) {
338            Ok(value) => {
339                if var.contains("TOKEN") {
340                    println!("  {}: {}***", var, &value[..8.min(value.len())]);
341                } else {
342                    println!("  {var}: {value}");
343                }
344            }
345            Err(_) => println!("  {}: {}", var, "Not set".dimmed()),
346        }
347    }
348
349    // Configuration file status
350    println!("\n{}", "Configuration:".bold().green());
351    match Config::from_file() {
352        Ok(_) => println!("  Configuration file: {}", "Found".green()),
353        Err(_) => println!("  Configuration file: {}", "Not found".red()),
354    }
355
356    Ok(())
357}
358
359async fn execute_rate_limit_test(bot: &Bot, requests: u32, delay_ms: u64) -> CliResult<()> {
360    println!(
361        "{} Testing rate limits with {} requests ({}ms delay)...",
362        emoji::ROCKET.bold().blue(),
363        requests,
364        delay_ms
365    );
366    println!();
367
368    let mut successful = 0;
369    let mut failed = 0;
370    let start_time = std::time::Instant::now();
371
372    for i in 1..=requests {
373        let request_start = std::time::Instant::now();
374
375        match bot.send_api_request(RequestSelfGet::new(())).await {
376            Ok(_) => {
377                successful += 1;
378                let duration = request_start.elapsed();
379                println!(
380                    "Request {}/{}: {} ({}ms)",
381                    i,
382                    requests,
383                    "OK".green(),
384                    duration.as_millis()
385                );
386            }
387            Err(e) => {
388                failed += 1;
389                println!("Request {}/{}: {} - {}", i, requests, "FAILED".red(), e);
390            }
391        }
392
393        if i < requests {
394            tokio::time::sleep(tokio::time::Duration::from_millis(delay_ms)).await;
395        }
396    }
397
398    let total_time = start_time.elapsed();
399
400    println!();
401    println!("{}", "Rate Limit Test Results:".bold().green());
402    println!("  Total requests: {requests}");
403    println!("  Successful: {}", successful.to_string().green());
404    println!("  Failed: {}", failed.to_string().red());
405    println!(
406        "  Success rate: {:.1}%",
407        (successful as f64 / requests as f64) * 100.0
408    );
409    println!("  Total time: {:.2}s", total_time.as_secs_f64());
410    println!(
411        "  Average rate: {:.1} req/s",
412        requests as f64 / total_time.as_secs_f64()
413    );
414
415    Ok(())
416}
417
418// Event handler for long polling
419async fn handle_event<T>(
420    bot: Bot,
421    result: T,
422) -> std::result::Result<(), vkteams_bot::error::BotError>
423where
424    T: serde::Serialize + std::fmt::Debug,
425{
426    debug!("Last event id: {:?}", bot.get_last_event_id());
427
428    if let Ok(json_str) = serde_json::to_string_pretty(&result) {
429        println!("{}", json_str.green());
430    } else {
431        println!("Event: {result:?}");
432    }
433
434    Ok(())
435}
436
437// Validation functions are now imported from utils/validation module
438
439// Structured output versions
440
441async fn execute_get_self_structured(bot: &Bot, detailed: bool) -> CliResponse<serde_json::Value> {
442    debug!("Getting bot information (structured)");
443
444    let request = RequestSelfGet::new(());
445    match bot.send_api_request(request).await {
446        Ok(result) => {
447            info!("Bot information retrieved successfully");
448            let data = if detailed {
449                serde_json::to_value(&result).unwrap_or(json!({}))
450            } else {
451                json!({
452                    "bot_id": result.user_id,
453                    "nickname": result.nick,
454                    "first_name": result.first_name,
455                    "about": result.about,
456                    "photo": result.photo
457                })
458            };
459            CliResponse::success("get-self", data)
460        }
461        Err(e) => CliResponse::error("get-self", format!("Failed to get bot info: {e}")),
462    }
463}
464
465async fn execute_get_events_structured(bot: &Bot, listen: bool) -> CliResponse<serde_json::Value> {
466    debug!("Getting events, listen mode: {}", listen);
467
468    if listen {
469        // For listen mode, we can't return structured data easily
470        // Return a message indicating the mode
471        CliResponse::success(
472            "get-events",
473            json!({
474                "mode": "listen",
475                "message": "Event listener started. Press Ctrl+C to stop.",
476                "note": "Use regular execute mode for event listening"
477            }),
478        )
479    } else {
480        match bot
481            .send_api_request(RequestEventsGet::new(bot.get_last_event_id()).with_poll_time(30))
482            .await
483        {
484            Ok(result) => {
485                info!("Successfully retrieved events");
486                let data = serde_json::to_value(&result).unwrap_or(json!({}));
487                CliResponse::success("get-events", data)
488            }
489            Err(e) => CliResponse::error("get-events", format!("Failed to get events: {e}")),
490        }
491    }
492}
493
494async fn execute_get_file_structured(
495    bot: &Bot,
496    file_id: &str,
497    file_path: &str,
498) -> CliResponse<serde_json::Value> {
499    debug!("Downloading file {} to {}", file_id, file_path);
500
501    match file_utils::download_and_save_file(bot, file_id, file_path).await {
502        Ok(downloaded_path) => {
503            info!("Successfully downloaded file with ID: {}", file_id);
504            let data = json!({
505                "file_id": file_id,
506                "download_path": downloaded_path.display().to_string(),
507                "status": "downloaded"
508            });
509            CliResponse::success("get-file", data)
510        }
511        Err(e) => CliResponse::error("get-file", format!("Failed to download file: {e}")),
512    }
513}
514
515async fn execute_health_check_structured(bot: &Bot) -> CliResponse<serde_json::Value> {
516    debug!("Performing health check");
517
518    let mut tests = Vec::new();
519    let mut all_passed = true;
520
521    // Test 1: Basic connectivity
522    let start = std::time::Instant::now();
523    let connectivity_result = match bot.send_api_request(RequestSelfGet::new(())).await {
524        Ok(_) => {
525            json!({
526                "name": "API Connectivity",
527                "status": "pass",
528                "latency_ms": start.elapsed().as_millis()
529            })
530        }
531        Err(e) => {
532            all_passed = false;
533            json!({
534                "name": "API Connectivity",
535                "status": "fail",
536                "error": e.to_string()
537            })
538        }
539    };
540    tests.push(connectivity_result);
541
542    // Test 2: Configuration check
543    let config_result = match Config::from_file() {
544        Ok(config) => {
545            if config.api.token.is_some() && config.api.url.is_some() {
546                json!({
547                    "name": "Configuration",
548                    "status": "pass",
549                    "details": "All required fields present"
550                })
551            } else {
552                all_passed = false;
553                json!({
554                    "name": "Configuration",
555                    "status": "fail",
556                    "error": "Missing required configuration"
557                })
558            }
559        }
560        Err(_) => {
561            all_passed = false;
562            json!({
563                "name": "Configuration",
564                "status": "fail",
565                "error": "Configuration file not found"
566            })
567        }
568    };
569    tests.push(config_result);
570
571    // Test 3: Network latency
572    let latency_start = std::time::Instant::now();
573    let latency_result = match bot.send_api_request(RequestSelfGet::new(())).await {
574        Ok(_) => {
575            let latency = latency_start.elapsed();
576            let status = if latency.as_millis() < 1000 {
577                "pass"
578            } else {
579                "warn"
580            };
581            json!({
582                "name": "Network Latency",
583                "status": status,
584                "latency_ms": latency.as_millis()
585            })
586        }
587        Err(e) => {
588            all_passed = false;
589            json!({
590                "name": "Network Latency",
591                "status": "fail",
592                "error": e.to_string()
593            })
594        }
595    };
596    tests.push(latency_result);
597
598    let data = json!({
599        "overall_status": if all_passed { "healthy" } else { "unhealthy" },
600        "tests": tests,
601        "timestamp": chrono::Utc::now().to_rfc3339()
602    });
603
604    CliResponse::success("health-check", data)
605}
606
607async fn execute_network_test_structured(bot: &Bot) -> CliResponse<serde_json::Value> {
608    debug!("Testing network connectivity");
609
610    let mut results = Vec::new();
611
612    // Test multiple endpoints
613    let endpoints = vec![("Bot Info", RequestSelfGet::new(()))];
614
615    for (name, request) in endpoints {
616        let start = std::time::Instant::now();
617
618        let result = match bot.send_api_request(request).await {
619            Ok(_) => {
620                json!({
621                    "endpoint": name,
622                    "status": "ok",
623                    "latency_ms": start.elapsed().as_millis()
624                })
625            }
626            Err(e) => {
627                json!({
628                    "endpoint": name,
629                    "status": "failed",
630                    "error": e.to_string()
631                })
632            }
633        };
634        results.push(result);
635    }
636
637    let data = json!({
638        "test_results": results,
639        "timestamp": chrono::Utc::now().to_rfc3339()
640    });
641
642    CliResponse::success("network-test", data)
643}
644
645async fn execute_system_info_structured() -> CliResponse<serde_json::Value> {
646    debug!("Gathering system information");
647
648    let mut env_vars = serde_json::Map::new();
649    let vars = [
650        "VKTEAMS_BOT_API_TOKEN",
651        "VKTEAMS_BOT_API_URL",
652        "VKTEAMS_PROXY",
653        "VKTEAMS_LOG_LEVEL",
654    ];
655
656    for var in &vars {
657        match std::env::var(var) {
658            Ok(value) => {
659                if var.contains("TOKEN") {
660                    env_vars.insert(
661                        var.to_string(),
662                        json!(format!("{}***", &value[..8.min(value.len())])),
663                    );
664                } else {
665                    env_vars.insert(var.to_string(), json!(value));
666                }
667            }
668            Err(_) => {
669                env_vars.insert(var.to_string(), json!("Not set"));
670            }
671        }
672    }
673
674    let config_status = match Config::from_file() {
675        Ok(_) => "found",
676        Err(_) => "not_found",
677    };
678
679    let data = json!({
680        "runtime": {
681            "os": std::env::consts::OS,
682            "architecture": std::env::consts::ARCH,
683            "family": std::env::consts::FAMILY,
684            "current_directory": std::env::current_dir().ok().map(|p| p.display().to_string())
685        },
686        "environment": env_vars,
687        "configuration": {
688            "status": config_status
689        }
690    });
691
692    CliResponse::success("system-info", data)
693}
694
695async fn execute_rate_limit_test_structured(
696    bot: &Bot,
697    requests: u32,
698    delay_ms: u64,
699) -> CliResponse<serde_json::Value> {
700    debug!("Testing rate limits with {} requests", requests);
701
702    let mut successful = 0;
703    let mut failed = 0;
704    let mut request_results = Vec::new();
705    let start_time = std::time::Instant::now();
706
707    for i in 1..=requests {
708        let request_start = std::time::Instant::now();
709
710        let result = match bot.send_api_request(RequestSelfGet::new(())).await {
711            Ok(_) => {
712                successful += 1;
713                json!({
714                    "request_number": i,
715                    "status": "success",
716                    "latency_ms": request_start.elapsed().as_millis()
717                })
718            }
719            Err(e) => {
720                failed += 1;
721                json!({
722                    "request_number": i,
723                    "status": "failed",
724                    "error": e.to_string()
725                })
726            }
727        };
728        request_results.push(result);
729
730        if i < requests {
731            tokio::time::sleep(tokio::time::Duration::from_millis(delay_ms)).await;
732        }
733    }
734
735    let total_time = start_time.elapsed();
736    let success_rate = (successful as f64 / requests as f64) * 100.0;
737    let average_rate = requests as f64 / total_time.as_secs_f64();
738
739    let data = json!({
740        "summary": {
741            "total_requests": requests,
742            "successful": successful,
743            "failed": failed,
744            "success_rate_percent": success_rate,
745            "total_time_seconds": total_time.as_secs_f64(),
746            "average_rate_per_second": average_rate,
747            "delay_between_requests_ms": delay_ms
748        },
749        "request_details": request_results
750    });
751
752    CliResponse::success("rate-limit-test", data)
753}
754
755// Utility functions
756
757#[cfg(test)]
758mod tests {
759    use super::*;
760    use tokio::runtime::Runtime;
761
762    fn dummy_bot() -> Bot {
763        Bot::with_params(&APIVersionUrl::V1, "dummy_token", "https://dummy.api.com").unwrap()
764    }
765
766    #[test]
767    fn test_validate_get_file_empty_file_id() {
768        let cmd = DiagnosticCommands::GetFile {
769            file_id: "".to_string(),
770            file_path: "/tmp".to_string(),
771        };
772        let res = cmd.validate();
773        assert!(res.is_err());
774    }
775
776    #[test]
777    fn test_validate_get_file_invalid_path() {
778        let cmd = DiagnosticCommands::GetFile {
779            file_id: "fileid".to_string(),
780            file_path: "".to_string(),
781        };
782        let res = cmd.validate();
783        assert!(res.is_ok()); // пустой путь допустим
784    }
785
786    #[test]
787    fn test_validate_rate_limit_zero() {
788        let cmd = DiagnosticCommands::RateLimitTest {
789            requests: 0,
790            delay_ms: 100,
791        };
792        let res = cmd.validate();
793        assert!(res.is_err());
794    }
795
796    #[test]
797    fn test_validate_rate_limit_too_many() {
798        let cmd = DiagnosticCommands::RateLimitTest {
799            requests: 1001,
800            delay_ms: 100,
801        };
802        let res = cmd.validate();
803        assert!(res.is_err());
804    }
805
806    #[test]
807    fn test_validate_rate_limit_valid() {
808        let cmd = DiagnosticCommands::RateLimitTest {
809            requests: 10,
810            delay_ms: 100,
811        };
812        let res = cmd.validate();
813        assert!(res.is_ok());
814    }
815
816    #[test]
817    fn test_execute_get_self_api_error() {
818        let cmd = DiagnosticCommands::GetSelf { detailed: true };
819        let bot = dummy_bot();
820        let rt = Runtime::new().unwrap();
821        let res = rt.block_on(cmd.execute(&bot));
822        assert!(res.is_err());
823    }
824
825    #[test]
826    fn test_execute_get_events_api_error() {
827        let cmd = DiagnosticCommands::GetEvents {
828            listen: Some(false),
829        };
830        let bot = dummy_bot();
831        let rt = Runtime::new().unwrap();
832        let res = rt.block_on(cmd.execute(&bot));
833        assert!(res.is_err());
834    }
835
836    #[test]
837    fn test_execute_get_file_api_error() {
838        let cmd = DiagnosticCommands::GetFile {
839            file_id: "fileid".to_string(),
840            file_path: "/tmp".to_string(),
841        };
842        let bot = dummy_bot();
843        let rt = Runtime::new().unwrap();
844        let res = rt.block_on(cmd.execute(&bot));
845        assert!(res.is_err());
846    }
847
848    #[test]
849    fn test_execute_health_check_api_error() {
850        let cmd = DiagnosticCommands::HealthCheck;
851        let bot = dummy_bot();
852        let rt = Runtime::new().unwrap();
853        let res = rt.block_on(cmd.execute(&bot));
854        assert!(res.is_ok());
855    }
856
857    #[test]
858    fn test_execute_network_test_api_error() {
859        let cmd = DiagnosticCommands::NetworkTest;
860        let bot = dummy_bot();
861        let rt = Runtime::new().unwrap();
862        let res = rt.block_on(cmd.execute(&bot));
863        assert!(res.is_ok());
864    }
865
866    #[test]
867    fn test_execute_system_info() {
868        let cmd = DiagnosticCommands::SystemInfo;
869        let bot = dummy_bot();
870        let rt = Runtime::new().unwrap();
871        // SystemInfo не требует bot, но для совместимости передаём
872        let res = rt.block_on(cmd.execute(&bot));
873        assert!(res.is_ok());
874    }
875
876    #[test]
877    fn test_execute_rate_limit_test_api_error() {
878        let cmd = DiagnosticCommands::RateLimitTest {
879            requests: 2,
880            delay_ms: 10,
881        };
882        let bot = dummy_bot();
883        let rt = Runtime::new().unwrap();
884        let res = rt.block_on(cmd.execute(&bot));
885        assert!(res.is_ok());
886    }
887
888    #[test]
889    fn test_diagnostic_commands_variants() {
890        let get_self = DiagnosticCommands::GetSelf { detailed: true };
891        assert_eq!(get_self.name(), "get-self");
892        if let DiagnosticCommands::GetSelf { detailed } = get_self {
893            assert!(detailed);
894        }
895
896        let get_events = DiagnosticCommands::GetEvents { listen: Some(true) };
897        assert_eq!(get_events.name(), "get-events");
898        if let DiagnosticCommands::GetEvents { listen } = get_events {
899            assert_eq!(listen, Some(true));
900        }
901
902        let get_file = DiagnosticCommands::GetFile {
903            file_id: "file123".to_string(),
904            file_path: "/tmp".to_string(),
905        };
906        assert_eq!(get_file.name(), "get-file");
907        if let DiagnosticCommands::GetFile { file_id, file_path } = get_file {
908            assert_eq!(file_id, "file123");
909            assert_eq!(file_path, "/tmp");
910        }
911
912        let health = DiagnosticCommands::HealthCheck;
913        assert_eq!(health.name(), "health-check");
914
915        let net = DiagnosticCommands::NetworkTest;
916        assert_eq!(net.name(), "network-test");
917
918        let sys = DiagnosticCommands::SystemInfo;
919        assert_eq!(sys.name(), "system-info");
920
921        let rate = DiagnosticCommands::RateLimitTest {
922            requests: 10,
923            delay_ms: 100,
924        };
925        assert_eq!(rate.name(), "rate-limit-test");
926        if let DiagnosticCommands::RateLimitTest { requests, delay_ms } = rate {
927            assert_eq!(requests, 10);
928            assert_eq!(delay_ms, 100);
929        }
930    }
931}