use mecha10_cli::commands::LogSource;
use mecha10_cli::services::{LogContentFilters, LogFileFilters, LogsService};
use std::collections::HashMap;
use tempfile::TempDir;
use tokio::io::AsyncWriteExt;
#[test]
fn test_logs_service_creation() {
let temp_dir = TempDir::new().unwrap();
let service = LogsService::new(temp_dir.path().to_path_buf());
assert_eq!(service.logs_dir(), &temp_dir.path().to_path_buf());
}
#[tokio::test]
async fn test_collect_log_files_empty_dir() {
let temp_dir = TempDir::new().unwrap();
let service = LogsService::new(temp_dir.path().join("logs"));
let filters = LogFileFilters {
node: None,
source: LogSource::All,
};
let files = service.collect_log_files(&filters).unwrap();
assert!(files.is_empty());
}
#[tokio::test]
async fn test_collect_log_files_with_files() {
let temp_dir = TempDir::new().unwrap();
let logs_dir = temp_dir.path().join("logs");
std::fs::create_dir_all(&logs_dir).unwrap();
std::fs::write(logs_dir.join("camera_driver.log"), "log content").unwrap();
std::fs::write(logs_dir.join("lidar_driver.log"), "log content").unwrap();
std::fs::write(logs_dir.join("framework.log"), "log content").unwrap();
let service = LogsService::new(logs_dir);
let filters = LogFileFilters {
node: None,
source: LogSource::All,
};
let files = service.collect_log_files(&filters).unwrap();
assert_eq!(files.len(), 3);
let filters = LogFileFilters {
node: Some("camera".to_string()),
source: LogSource::All,
};
let files = service.collect_log_files(&filters).unwrap();
assert_eq!(files.len(), 1);
assert!(files.contains_key("camera_driver"));
let filters = LogFileFilters {
node: None,
source: LogSource::Framework,
};
let files = service.collect_log_files(&filters).unwrap();
assert_eq!(files.len(), 1);
assert!(files.contains_key("framework"));
}
#[tokio::test]
async fn test_collect_log_files_ignores_non_log_files() {
let temp_dir = TempDir::new().unwrap();
let logs_dir = temp_dir.path().join("logs");
std::fs::create_dir_all(&logs_dir).unwrap();
std::fs::write(logs_dir.join("test.log"), "log content").unwrap();
std::fs::write(logs_dir.join("test.txt"), "not a log").unwrap();
std::fs::write(logs_dir.join("README.md"), "docs").unwrap();
let service = LogsService::new(logs_dir);
let filters = LogFileFilters {
node: None,
source: LogSource::All,
};
let files = service.collect_log_files(&filters).unwrap();
assert_eq!(files.len(), 1);
assert!(files.contains_key("test"));
}
#[tokio::test]
async fn test_collect_log_files_source_filter_nodes() {
let temp_dir = TempDir::new().unwrap();
let logs_dir = temp_dir.path().join("logs");
std::fs::create_dir_all(&logs_dir).unwrap();
std::fs::write(logs_dir.join("camera_driver.log"), "node log").unwrap();
std::fs::write(logs_dir.join("lidar_driver.log"), "node log").unwrap();
std::fs::write(logs_dir.join("framework.log"), "framework log").unwrap();
std::fs::write(logs_dir.join("redis_service.log"), "service log").unwrap();
let service = LogsService::new(logs_dir);
let filters = LogFileFilters {
node: None,
source: LogSource::Nodes,
};
let files = service.collect_log_files(&filters).unwrap();
assert_eq!(files.len(), 2);
assert!(files.contains_key("camera_driver"));
assert!(files.contains_key("lidar_driver"));
}
#[tokio::test]
async fn test_collect_log_files_source_filter_services() {
let temp_dir = TempDir::new().unwrap();
let logs_dir = temp_dir.path().join("logs");
std::fs::create_dir_all(&logs_dir).unwrap();
std::fs::write(logs_dir.join("camera_driver.log"), "node log").unwrap();
std::fs::write(logs_dir.join("redis_service.log"), "service log").unwrap();
std::fs::write(logs_dir.join("postgres_service.log"), "service log").unwrap();
let service = LogsService::new(logs_dir);
let filters = LogFileFilters {
node: None,
source: LogSource::Services,
};
let files = service.collect_log_files(&filters).unwrap();
assert_eq!(files.len(), 2);
assert!(files.contains_key("redis_service"));
assert!(files.contains_key("postgres_service"));
}
#[tokio::test]
async fn test_read_logs() {
let temp_dir = TempDir::new().unwrap();
let logs_dir = temp_dir.path().join("logs");
std::fs::create_dir_all(&logs_dir).unwrap();
let log_path = logs_dir.join("test.log");
let mut file = tokio::fs::File::create(&log_path).await.unwrap();
file.write_all(b"INFO line 1\nDEBUG line 2\nERROR line 3\n")
.await
.unwrap();
file.flush().await.unwrap();
let service = LogsService::new(logs_dir);
let mut sources = HashMap::new();
sources.insert("test".to_string(), log_path);
let filters = LogContentFilters {
pattern: None,
level: None,
lines: None,
};
let lines = service.read_logs(&sources, &filters).await.unwrap();
assert_eq!(lines.len(), 3);
assert_eq!(lines[0].source_name, "test");
assert!(lines[0].content.contains("INFO line 1"));
let filters = LogContentFilters {
pattern: None,
level: Some("ERROR".to_string()),
lines: None,
};
let lines = service.read_logs(&sources, &filters).await.unwrap();
assert_eq!(lines.len(), 1);
assert!(lines[0].content.contains("ERROR"));
let filters = LogContentFilters {
pattern: None,
level: None,
lines: Some(2),
};
let lines = service.read_logs(&sources, &filters).await.unwrap();
assert_eq!(lines.len(), 2);
}
#[tokio::test]
async fn test_read_logs_with_pattern_filter() {
let temp_dir = TempDir::new().unwrap();
let logs_dir = temp_dir.path().join("logs");
std::fs::create_dir_all(&logs_dir).unwrap();
let log_path = logs_dir.join("test.log");
let mut file = tokio::fs::File::create(&log_path).await.unwrap();
file.write_all(b"Starting camera\nCapturing frame\nCamera error occurred\n")
.await
.unwrap();
file.flush().await.unwrap();
let service = LogsService::new(logs_dir);
let mut sources = HashMap::new();
sources.insert("test".to_string(), log_path);
let filters = LogContentFilters {
pattern: Some("camera".to_string()),
level: None,
lines: None,
};
let lines = service.read_logs(&sources, &filters).await.unwrap();
assert_eq!(lines.len(), 2);
}
#[tokio::test]
async fn test_read_logs_multiple_sources() {
let temp_dir = TempDir::new().unwrap();
let logs_dir = temp_dir.path().join("logs");
std::fs::create_dir_all(&logs_dir).unwrap();
let camera_log = logs_dir.join("camera.log");
let lidar_log = logs_dir.join("lidar.log");
tokio::fs::write(&camera_log, "Camera line 1\nCamera line 2\n").await.unwrap();
tokio::fs::write(&lidar_log, "Lidar line 1\n").await.unwrap();
let service = LogsService::new(logs_dir);
let mut sources = HashMap::new();
sources.insert("camera".to_string(), camera_log);
sources.insert("lidar".to_string(), lidar_log);
let filters = LogContentFilters {
pattern: None,
level: None,
lines: None,
};
let lines = service.read_logs(&sources, &filters).await.unwrap();
assert_eq!(lines.len(), 3);
}
#[test]
fn test_should_display_line() {
let temp_dir = TempDir::new().unwrap();
let service = LogsService::new(temp_dir.path().to_path_buf());
let filters = LogContentFilters {
pattern: None,
level: None,
lines: None,
};
assert!(service.should_display_line("any line", &filters));
let filters = LogContentFilters {
pattern: Some("ERROR".to_string()),
level: None,
lines: None,
};
assert!(service.should_display_line("ERROR: something failed", &filters));
assert!(!service.should_display_line("INFO: all good", &filters));
let filters = LogContentFilters {
pattern: None,
level: Some("WARN".to_string()),
lines: None,
};
assert!(service.should_display_line("WARN: warning message", &filters));
assert!(!service.should_display_line("INFO: info message", &filters));
let filters = LogContentFilters {
pattern: Some("error".to_string()),
level: None,
lines: None,
};
assert!(service.should_display_line("ERROR: something failed", &filters));
}
#[test]
fn test_should_display_line_combined_filters() {
let temp_dir = TempDir::new().unwrap();
let service = LogsService::new(temp_dir.path().to_path_buf());
let filters = LogContentFilters {
pattern: Some("connection".to_string()),
level: Some("ERROR".to_string()),
lines: None,
};
assert!(service.should_display_line("ERROR: connection failed", &filters));
assert!(!service.should_display_line("ERROR: database failed", &filters));
assert!(!service.should_display_line("INFO: connection established", &filters));
}
#[test]
fn test_open_for_follow() {
let temp_dir = TempDir::new().unwrap();
let logs_dir = temp_dir.path().join("logs");
std::fs::create_dir_all(&logs_dir).unwrap();
let log_path = logs_dir.join("test.log");
std::fs::write(&log_path, "existing content\n").unwrap();
let service = LogsService::new(logs_dir);
let mut sources = HashMap::new();
sources.insert("test".to_string(), log_path);
let readers = service.open_for_follow(&sources).unwrap();
assert_eq!(readers.len(), 1);
assert!(readers.contains_key("test"));
}
#[test]
fn test_read_new_lines() {
use std::io::Write;
let temp_dir = TempDir::new().unwrap();
let logs_dir = temp_dir.path().join("logs");
std::fs::create_dir_all(&logs_dir).unwrap();
let log_path = logs_dir.join("test.log");
let mut file = std::fs::File::create(&log_path).unwrap();
file.write_all(b"initial line\n").unwrap();
file.flush().unwrap();
drop(file);
let service = LogsService::new(logs_dir);
let mut sources = HashMap::new();
sources.insert("test".to_string(), log_path.clone());
let mut readers = service.open_for_follow(&sources).unwrap();
let reader = readers.get_mut("test").unwrap();
let filters = LogContentFilters {
pattern: None,
level: None,
lines: None,
};
let lines = service.read_new_lines(reader, &filters).unwrap();
assert_eq!(lines.len(), 0);
let mut file = std::fs::OpenOptions::new()
.append(true)
.open(&log_path)
.unwrap();
file.write_all(b"new line 1\nnew line 2\n").unwrap();
file.flush().unwrap();
drop(file);
let lines = service.read_new_lines(reader, &filters).unwrap();
assert_eq!(lines.len(), 2);
assert!(lines[0].contains("new line 1"));
}