Skip to content

Commit 2282cfa

Browse files
committed
test: strengthen error-path and persistence semantics coverage
1 parent 555e64b commit 2282cfa

File tree

3 files changed

+725
-33
lines changed

3 files changed

+725
-33
lines changed

src/bot.rs

Lines changed: 168 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use anyhow::Context;
88
use bytes::Bytes;
99
use dashmap::DashMap;
1010
use futures_util::{StreamExt, TryStreamExt};
11-
use sysinfo::System;
11+
use sysinfo::{ProcessRefreshKind, ProcessesToUpdate, System, get_current_pid};
1212
use teloxide::prelude::*;
1313
use teloxide::sugar::request::RequestLinkPreviewExt;
1414
use teloxide::types::{
@@ -104,21 +104,22 @@ struct SpeedSnapshot {
104104
#[derive(Debug, Clone, Copy, Default)]
105105
struct ResourceSnapshot {
106106
cpu_percent: f32,
107-
used_memory_mb: u64,
108-
total_memory_mb: u64,
109-
available_memory_mb: u64,
107+
system_used_memory_mb: u64,
108+
system_total_memory_mb: u64,
109+
bot_memory_mb: Option<u64>,
110110
}
111111

112112
static STATUS_RESOURCE_CACHE: LazyLock<std::sync::Mutex<(System, Instant, ResourceSnapshot)>> =
113113
LazyLock::new(|| {
114114
let mut system = System::new();
115115
system.refresh_cpu_usage();
116116
system.refresh_memory();
117+
let bot_memory_mb = sample_current_process_memory_mb(&mut system);
117118
let snapshot = ResourceSnapshot {
118119
cpu_percent: system.global_cpu_usage(),
119-
used_memory_mb: system.used_memory() / (1024 * 1024),
120-
total_memory_mb: system.total_memory() / (1024 * 1024),
121-
available_memory_mb: system.available_memory() / (1024 * 1024),
120+
system_used_memory_mb: system.used_memory() / (1024 * 1024),
121+
system_total_memory_mb: system.total_memory() / (1024 * 1024),
122+
bot_memory_mb,
122123
};
123124
std::sync::Mutex::new((system, Instant::now(), snapshot))
124125
});
@@ -388,17 +389,75 @@ fn sample_resource_snapshot() -> ResourceSnapshot {
388389
if last_refresh.elapsed() >= STATUS_RESOURCE_REFRESH_INTERVAL {
389390
system.refresh_cpu_usage();
390391
system.refresh_memory();
392+
let bot_memory_mb = sample_current_process_memory_mb(system);
391393
*snapshot = ResourceSnapshot {
392394
cpu_percent: system.global_cpu_usage(),
393-
used_memory_mb: system.used_memory() / (1024 * 1024),
394-
total_memory_mb: system.total_memory() / (1024 * 1024),
395-
available_memory_mb: system.available_memory() / (1024 * 1024),
395+
system_used_memory_mb: system.used_memory() / (1024 * 1024),
396+
system_total_memory_mb: system.total_memory() / (1024 * 1024),
397+
bot_memory_mb,
396398
};
397399
*last_refresh = Instant::now();
398400
}
399401
*snapshot
400402
}
401403

404+
fn sample_current_process_memory_mb(system: &mut System) -> Option<u64> {
405+
let current_pid = get_current_pid().ok()?;
406+
let pids = [current_pid];
407+
system.refresh_processes_specifics(
408+
ProcessesToUpdate::Some(&pids),
409+
false,
410+
ProcessRefreshKind::nothing().with_memory(),
411+
);
412+
system
413+
.process(current_pid)
414+
.map(|process| process.memory() / (1024 * 1024))
415+
}
416+
417+
fn format_bot_memory(bot_memory_mb: Option<u64>) -> String {
418+
bot_memory_mb
419+
.map(|mb| format!("{mb} MB"))
420+
.unwrap_or_else(|| "n/a".to_string())
421+
}
422+
423+
fn build_status_text(
424+
total_count: i64,
425+
user_count: i64,
426+
chat_count: i64,
427+
cache_snapshot: CacheSnapshot,
428+
resource_snapshot: ResourceSnapshot,
429+
uptime: &str,
430+
download_line: &str,
431+
upload_line: &str,
432+
) -> String {
433+
format!(
434+
"Status\n\n\
435+
Cache:\n\
436+
Total: {total_count}\n\
437+
User: {user_count}\n\
438+
Chat: {chat_count}\n\n\
439+
Runtime Cache:\n\
440+
Hits: {hits}\n\
441+
Misses: {misses}\n\
442+
Hit Rate: {hit_rate:.2}%\n\n\
443+
Resources:\n\
444+
CPU: {cpu:.1}%\n\
445+
System Memory: {system_used} / {system_total} MB\n\
446+
Bot Memory: {bot_memory}\n\
447+
Uptime: {uptime}\n\n\
448+
Transfer:\n\
449+
{download_line}\n\
450+
{upload_line}",
451+
hits = cache_snapshot.hits,
452+
misses = cache_snapshot.misses,
453+
hit_rate = cache_snapshot.hit_rate_percent,
454+
cpu = resource_snapshot.cpu_percent,
455+
system_used = resource_snapshot.system_used_memory_mb,
456+
system_total = resource_snapshot.system_total_memory_mb,
457+
bot_memory = format_bot_memory(resource_snapshot.bot_memory_mb),
458+
)
459+
}
460+
402461
fn format_uptime(duration: std::time::Duration) -> String {
403462
let secs = duration.as_secs();
404463
let hours = secs / 3600;
@@ -2673,6 +2732,68 @@ mod tests {
26732732
assert!(ok);
26742733
}
26752734

2735+
#[test]
2736+
fn parse_telegram_api_response_returns_error_when_http_200_ok_false() {
2737+
let body = r#"{"ok": false, "description": "chat not found"}"#;
2738+
let err = super::parse_telegram_api_response(body, reqwest::StatusCode::OK, "sendAudio")
2739+
.expect_err("ok=false should be treated as Telegram API error");
2740+
let err_msg = err.to_string();
2741+
2742+
assert!(err_msg.contains("chat not found"));
2743+
assert!(err_msg.contains("HTTP 200"));
2744+
}
2745+
2746+
#[test]
2747+
fn parse_telegram_api_response_returns_error_when_http_500_for_any_ok_flag() {
2748+
let cases = [
2749+
(r#"{"ok": true, "result": {}}"#, "unknown error"),
2750+
(
2751+
r#"{"ok": false, "description": "server failed"}"#,
2752+
"server failed",
2753+
),
2754+
];
2755+
2756+
for (body, expected_desc) in cases {
2757+
let err = super::parse_telegram_api_response(
2758+
body,
2759+
reqwest::StatusCode::INTERNAL_SERVER_ERROR,
2760+
"sendAudio",
2761+
)
2762+
.expect_err("non-2xx status should always be treated as error");
2763+
let err_msg = err.to_string();
2764+
2765+
assert!(err_msg.contains(expected_desc));
2766+
assert!(err_msg.contains("HTTP 500"));
2767+
}
2768+
}
2769+
2770+
#[test]
2771+
fn parse_telegram_api_response_returns_parse_error_for_non_json_body() {
2772+
let err = super::parse_telegram_api_response(
2773+
"<html>502 bad gateway</html>",
2774+
reqwest::StatusCode::OK,
2775+
"sendAudio",
2776+
)
2777+
.expect_err("non-JSON body should fail response parsing");
2778+
let err_msg = err.to_string();
2779+
2780+
assert!(err_msg.contains("Failed to parse upload response"));
2781+
}
2782+
2783+
#[test]
2784+
fn parse_telegram_api_response_uses_unknown_error_when_description_missing() {
2785+
let err = super::parse_telegram_api_response(
2786+
r#"{"ok": false}"#,
2787+
reqwest::StatusCode::OK,
2788+
"sendAudio",
2789+
)
2790+
.expect_err("missing description should still return a Telegram API error");
2791+
let err_msg = err.to_string();
2792+
2793+
assert!(err_msg.contains("unknown error"));
2794+
assert!(err_msg.contains("HTTP 200"));
2795+
}
2796+
26762797
#[test]
26772798
fn extract_file_id_reads_audio_field() {
26782799
let payload = serde_json::json!({
@@ -2852,6 +2973,34 @@ mod tests {
28522973
let line = super::format_speed_line("Download", None);
28532974
assert!(line.contains("no non-cache samples"));
28542975
}
2976+
2977+
#[test]
2978+
fn status_text_uses_section_layout_and_split_memory_fields() {
2979+
let cache_snapshot = super::CacheSnapshot {
2980+
hits: 9,
2981+
misses: 3,
2982+
hit_rate_percent: 75.0,
2983+
};
2984+
let resource_snapshot = super::ResourceSnapshot {
2985+
cpu_percent: 12.5,
2986+
system_used_memory_mb: 512,
2987+
system_total_memory_mb: 1024,
2988+
bot_memory_mb: Some(12),
2989+
};
2990+
let text = super::build_status_text(
2991+
100,
2992+
20,
2993+
8,
2994+
cache_snapshot,
2995+
resource_snapshot,
2996+
"00:10:00",
2997+
"Download speed: 6.00 MB/s",
2998+
"Upload speed: 2.00 MB/s",
2999+
);
3000+
assert!(text.contains("Cache:\nTotal: 100\nUser: 20\nChat: 8"));
3001+
assert!(text.contains("System Memory: 512 / 1024 MB"));
3002+
assert!(text.contains("Bot Memory: 12 MB"));
3003+
}
28553004
}
28563005

28573006
async fn handle_music_url(
@@ -3141,27 +3290,15 @@ async fn handle_status_command(
31413290
let uptime = format_uptime(state.runtime_metrics.uptime());
31423291
let download_line = format_speed_line("Download", download_speed);
31433292
let upload_line = format_speed_line("Upload", upload_speed);
3144-
3145-
let status_text = format!(
3146-
"Status\n\n\
3147-
Cache total: {total_count}\n\
3148-
Cache user: {user_count}\n\
3149-
Cache chat: {chat_count}\n\n\
3150-
Runtime cache hits: {hits}\n\
3151-
Runtime cache misses: {misses}\n\
3152-
Runtime hit rate: {hit_rate:.2}%\n\n\
3153-
CPU usage: {cpu:.1}%\n\
3154-
Memory: {used} / {total} MB (available {available} MB)\n\
3155-
Uptime: {uptime}\n\n\
3156-
{download_line}\n\
3157-
{upload_line}",
3158-
hits = cache_snapshot.hits,
3159-
misses = cache_snapshot.misses,
3160-
hit_rate = cache_snapshot.hit_rate_percent,
3161-
cpu = resource_snapshot.cpu_percent,
3162-
used = resource_snapshot.used_memory_mb,
3163-
total = resource_snapshot.total_memory_mb,
3164-
available = resource_snapshot.available_memory_mb
3293+
let status_text = build_status_text(
3294+
total_count,
3295+
user_count,
3296+
chat_count,
3297+
cache_snapshot,
3298+
resource_snapshot,
3299+
&uptime,
3300+
&download_line,
3301+
&upload_line,
31653302
);
31663303

31673304
bot.send_message(msg.chat.id, status_text)

0 commit comments

Comments
 (0)