use std::fs;
use std::path::Path;
use std::path::PathBuf;
use futures::TryStreamExt;
use http::StatusCode;
use http_body_util::BodyExt;
use serde_json::Value;
use tempfile::tempdir;
use tokio::io::AsyncReadExt;
use super::*;
#[cfg(target_family = "unix")]
#[tokio::test]
async fn test_handle_status() {
let temp_dir = tempdir().expect("Failed to create temp dir");
let output_path = temp_dir.path();
let service = MemoryService::new(output_path);
let response = service.handle_status().await;
assert!(response.is_ok(), "Status request should succeed");
let response = response.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let body_bytes = response.into_body();
let body_data = body_bytes.collect().await.unwrap().to_bytes();
let json: Value = serde_json::from_slice(&body_data).expect("Response should be valid JSON");
assert!(
json.get("profiling_active").is_some(),
"Should have profiling_active field"
);
assert!(json.get("status").is_some(), "Should have status field");
}
#[cfg(target_family = "unix")]
#[tokio::test]
async fn test_handle_start() {
let temp_dir = tempdir().expect("Failed to create temp dir");
let output_path = temp_dir.path();
let service = MemoryService::new(output_path);
let response = service.handle_start().await;
match response {
Ok(resp) => {
#[cfg(all(target_family = "unix", feature = "global-allocator"))]
{
assert!(
resp.status() == StatusCode::OK
|| resp.status() == StatusCode::INTERNAL_SERVER_ERROR,
"Expected OK or INTERNAL_SERVER_ERROR, got: {}",
resp.status()
);
}
#[cfg(not(all(target_family = "unix", feature = "global-allocator")))]
{
assert_eq!(resp.status(), StatusCode::NOT_IMPLEMENTED);
}
let body_bytes = resp.into_body();
let body_data = body_bytes.collect().await.unwrap().to_bytes();
let json: Value =
serde_json::from_slice(&body_data).expect("Response should be valid JSON");
assert!(json.get("status").is_some(), "Should have status field");
assert!(json.get("message").is_some(), "Should have message field");
}
Err(_) => {
}
}
}
#[cfg(target_family = "unix")]
#[tokio::test]
async fn test_handle_stop() {
let temp_dir = tempdir().expect("Failed to create temp dir");
let output_path = temp_dir.path();
let service = MemoryService::new(output_path);
let response = service.handle_stop().await;
match response {
Ok(resp) => {
#[cfg(all(target_family = "unix", feature = "global-allocator"))]
{
assert!(
resp.status() == StatusCode::OK
|| resp.status() == StatusCode::INTERNAL_SERVER_ERROR,
"Expected OK or INTERNAL_SERVER_ERROR, got: {}",
resp.status()
);
}
#[cfg(not(all(target_family = "unix", feature = "global-allocator")))]
{
assert_eq!(resp.status(), StatusCode::NOT_IMPLEMENTED);
}
let body_bytes = resp.into_body();
let body_data = body_bytes.collect().await.unwrap().to_bytes();
let json: Value =
serde_json::from_slice(&body_data).expect("Response should be valid JSON");
assert!(json.get("status").is_some(), "Should have status field");
assert!(json.get("message").is_some(), "Should have message field");
}
Err(_) => {
}
}
}
#[cfg(target_family = "unix")]
#[tokio::test]
async fn test_handle_dump_creates_directory() {
let temp_dir = tempdir().expect("Failed to create temp dir");
let output_path = temp_dir.path();
let service = MemoryService::new(output_path);
let memory_path = output_path.join("memory");
assert!(
!memory_path.exists(),
"Memory directory should not exist initially"
);
let response = service.handle_dump().await;
match response {
Ok(resp) => {
#[cfg(all(target_family = "unix", feature = "global-allocator"))]
{
assert!(
resp.status() == StatusCode::OK
|| resp.status() == StatusCode::INTERNAL_SERVER_ERROR,
"Expected OK or INTERNAL_SERVER_ERROR, got: {}",
resp.status()
);
}
#[cfg(not(all(target_family = "unix", feature = "global-allocator")))]
{
assert_eq!(resp.status(), StatusCode::NOT_IMPLEMENTED);
}
let status = resp.status();
let body_bytes = resp.into_body();
let body_data = body_bytes.collect().await.unwrap().to_bytes();
let json: Value =
serde_json::from_slice(&body_data).expect("Response should be valid JSON");
assert!(json.get("status").is_some(), "Should have status field");
#[cfg(all(target_family = "unix", feature = "global-allocator"))]
{
if status == StatusCode::OK {
assert!(
json.get("dump_path").is_some(),
"Should have dump_path field"
);
}
}
#[cfg(all(target_family = "unix", feature = "global-allocator"))]
{
if status == StatusCode::OK {
let dump_path = json.get("dump_path").unwrap().as_str().unwrap();
assert!(
dump_path.contains("/memory/"),
"Dump path should be in memory subdirectory"
);
assert!(
dump_path.contains("router_heap_dump_"),
"Should have correct filename pattern"
);
assert!(dump_path.ends_with(".prof"), "Should have .prof extension");
}
}
}
Err(_) => {
}
}
#[cfg(all(target_family = "unix", feature = "global-allocator"))]
assert!(memory_path.exists(), "Memory directory should be created");
#[cfg(not(all(target_family = "unix", feature = "global-allocator")))]
{
}
}
#[cfg(target_family = "unix")]
#[tokio::test]
async fn test_add_to_archive_with_files() {
let temp_dir = tempdir().expect("Failed to create temp dir");
let output_path = temp_dir.path();
let memory_path = Path::new(&output_path).join("memory");
fs::create_dir_all(&memory_path).expect("Failed to create memory directory");
fs::write(memory_path.join("test_heap.prof"), b"test heap dump data")
.expect("Failed to write test file");
fs::write(memory_path.join("test_profile.prof"), b"test profile data")
.expect("Failed to write another test file");
let (mut reader, writer) = tokio::io::duplex(1024 * 1024);
let mut tar = tokio_tar::Builder::new(writer);
let result = MemoryService::add_to_archive(&mut tar, output_path).await;
assert!(result.is_ok(), "Adding to archive should succeed");
tar.finish().await.expect("Should be able to finish tar");
drop(tar);
let mut archive_data = Vec::new();
reader
.read_to_end(&mut archive_data)
.await
.expect("Should be able to read data");
assert!(!archive_data.is_empty(), "Archive should contain data");
let mut archive = tokio_tar::Archive::new(archive_data.as_slice());
let mut entries_stream = archive.entries().expect("Should be able to read entries");
let mut entries = Vec::new();
while let Some(entry) = entries_stream.try_next().await.expect("Should read entry") {
entries.push(entry);
}
assert!(!entries.is_empty(), "Archive should contain entries");
let paths: Vec<String> = entries
.iter()
.map(|entry: &tokio_tar::Entry<_>| entry.path().unwrap().to_string_lossy().to_string())
.collect();
assert!(
paths.iter().any(|p| p.contains("memory/")),
"Should contain memory directory entries"
);
}
#[cfg(target_family = "unix")]
#[tokio::test]
async fn test_add_to_archive_empty_directory() {
let temp_dir = tempdir().expect("Failed to create temp dir");
let output_path = temp_dir.path();
let (mut reader, writer) = tokio::io::duplex(1024 * 1024);
let mut tar = tokio_tar::Builder::new(writer);
let result = MemoryService::add_to_archive(&mut tar, output_path).await;
assert!(result.is_ok(), "Adding empty directory should succeed");
tar.finish().await.expect("Should be able to finish tar");
drop(tar);
let mut archive_data = Vec::new();
reader
.read_to_end(&mut archive_data)
.await
.expect("Should be able to read data");
let mut archive = tokio_tar::Archive::new(archive_data.as_slice());
let mut entries_stream = archive.entries().expect("Should be able to read entries");
let mut entries = Vec::new();
while let Some(entry) = entries_stream.try_next().await.expect("Should read entry") {
entries.push(entry);
}
assert_eq!(
entries.len(),
1,
"Should have exactly one entry (empty directory)"
);
let entry: &tokio_tar::Entry<_> = &entries[0];
let path = entry
.path()
.expect("Should have path")
.to_string_lossy()
.to_string();
assert_eq!(path, "memory/", "Should create empty memory directory");
assert!(
entry.header().entry_type().is_dir(),
"Should be a directory entry"
);
}
#[cfg(target_family = "unix")]
#[test]
fn test_memory_service_clone() {
let service1 = MemoryService::new(&PathBuf::from("/tmp/test1"));
let service2 = service1.clone();
assert_eq!(service1.output_directory, service2.output_directory);
}
#[cfg(target_family = "unix")]
#[test]
fn test_path_handling() {
let temp_dir = tempdir().expect("Failed to create temp dir");
let base_path = temp_dir.path().to_str().unwrap();
let memory_path = Path::new(base_path).join("memory");
assert_eq!(memory_path, Path::new(&format!("{}/memory", base_path)));
let display_str = format!("{}", memory_path.display());
assert!(display_str.ends_with("/memory"));
}