#[tokio::test]
async fn test_temp_file_security() {
use tempfile::NamedTempFile;
let mut temp_files = Vec::new();
for _ in 0..10 {
let temp_file = NamedTempFile::with_suffix(".bam").expect("Failed to create temp file");
let path = temp_file.path().to_string_lossy();
assert!(!path.contains("ref_solver_temp"));
assert!(!path.contains(std::process::id().to_string().as_str()));
let metadata = std::fs::metadata(temp_file.path()).expect("Failed to get metadata");
let permissions = metadata.permissions();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mode = permissions.mode();
assert_eq!(
mode & 0o777,
0o600,
"Temp file should have owner-only permissions"
);
}
temp_files.push(temp_file);
}
let paths: Vec<String> = temp_files
.iter()
.map(|f| f.path().to_string_lossy().to_string())
.collect();
let unique_paths: std::collections::HashSet<_> = paths.iter().collect();
assert_eq!(
paths.len(),
unique_paths.len(),
"All temp file names should be unique"
);
}
#[test]
fn test_filename_validation_security() {
use ref_solver::utils::validation::{validate_filename, ValidationError};
let traversal_attempts = vec![
"../etc/passwd",
"..\\windows\\system32",
"test/../../secret",
"normal/../../../etc/passwd",
"..\\..\\..\\windows\\system.ini",
];
for attempt in traversal_attempts {
match validate_filename(attempt) {
Err(ValidationError::InvalidFilename) => {
}
Ok(_) => panic!("Directory traversal attempt '{attempt}' should have been blocked"),
Err(e) => panic!("Unexpected error for '{attempt}': {e:?}"),
}
}
let null_byte_attempts = vec!["test\0.txt", "normal.txt\0", "file\x00name.txt"];
for attempt in null_byte_attempts {
assert!(
validate_filename(attempt).is_err(),
"Null byte injection '{attempt}' should be blocked"
);
}
let control_char_attempts = vec!["test\x01.txt", "file\x1f.txt", "name\x0b.txt"];
for attempt in control_char_attempts {
assert!(
validate_filename(attempt).is_err(),
"Control character injection '{attempt}' should be blocked"
);
}
let valid_tests = vec![
("test.sam", "test.sam"),
("my-file_123.bam", "my-file_123.bam"),
("test@#$%file.txt", "testfile.txt"), ("sample 123.vcf", "sample 123.vcf"), ];
for (input, expected) in valid_tests {
match validate_filename(input) {
Ok(sanitized) => assert_eq!(sanitized, expected, "Sanitization failed for '{input}'"),
Err(e) => panic!("Valid filename '{input}' should be accepted: {e:?}"),
}
}
}
#[test]
#[allow(clippy::similar_names)] fn test_file_format_validation() {
use ref_solver::utils::validation::validate_file_format;
use ref_solver::web::format_detection::FileFormat;
let valid_bam = b"BAM\x01test_content";
assert!(validate_file_format(valid_bam, FileFormat::Bam));
let invalid_bam = b"NOTBAM\x01test";
assert!(!validate_file_format(invalid_bam, FileFormat::Bam));
let valid_cram = b"CRAMtest_content";
assert!(validate_file_format(valid_cram, FileFormat::Cram));
let invalid_cram = b"NOTCRAM";
assert!(!validate_file_format(invalid_cram, FileFormat::Cram));
let valid_vcf = b"##fileformat=VCFv4.2\n##contig=<ID=chr1>";
assert!(validate_file_format(valid_vcf, FileFormat::Vcf));
let invalid_vcf = b"@SQ\tSN:chr1\tLN:123"; assert!(!validate_file_format(invalid_vcf, FileFormat::Vcf));
let valid_sam = b"@SQ\tSN:chr1\tLN:123456";
assert!(validate_file_format(valid_sam, FileFormat::Sam));
let valid_sam2 = b"@HD\tVN:1.0\tSO:coordinate\n@SQ\tSN:chr1\tLN:123";
assert!(validate_file_format(valid_sam2, FileFormat::Sam));
assert!(!validate_file_format(b"", FileFormat::Bam));
assert!(!validate_file_format(b"", FileFormat::Sam));
assert!(!validate_file_format(b"", FileFormat::Vcf));
}
#[test]
fn test_content_integrity_validation() {
use ref_solver::utils::validation::validate_file_content;
let valid_text = b"@SQ\tSN:chr1\tLN:123456\n@SQ\tSN:chr2\tLN:654321";
assert!(validate_file_content(valid_text, true).is_ok());
let binary_data = vec![0u8; 1000]; assert!(validate_file_content(&binary_data, true).is_err());
let binary_data = vec![0xABu8; 100];
assert!(validate_file_content(&binary_data, false).is_ok());
assert!(validate_file_content(b"", true).is_err());
assert!(validate_file_content(b"", false).is_err());
let mixed_content = b"@SQ\tSN:chr1\tLN:123\n\x00\x01valid text content";
assert!(validate_file_content(mixed_content, true).is_ok());
let mut high_binary_content = vec![0u8; 500]; high_binary_content.extend_from_slice(b"some text"); assert!(validate_file_content(&high_binary_content, true).is_err()); }
#[test]
fn test_comprehensive_upload_validation() {
use ref_solver::utils::validation::validate_upload;
use ref_solver::web::format_detection::FileFormat;
let sam_content = b"@SQ\tSN:chr1\tLN:123456";
let result = validate_upload(Some("test.sam"), sam_content, FileFormat::Sam);
assert!(result.is_ok());
assert_eq!(result.unwrap().unwrap(), "test.sam");
let result = validate_upload(None, sam_content, FileFormat::Sam);
assert!(result.is_ok());
assert!(result.unwrap().is_none());
let result = validate_upload(Some("../etc/passwd"), sam_content, FileFormat::Sam);
assert!(result.is_err());
let bam_content = b"BAM\x01test";
let result = validate_upload(Some("test.sam"), bam_content, FileFormat::Sam);
assert!(result.is_err());
let result = validate_upload(Some("test.txt"), sam_content, FileFormat::Auto);
assert!(result.is_ok());
}
#[test]
fn test_error_sanitization() {
use ref_solver::web::server::{create_safe_error_response, ErrorType};
let error_response = create_safe_error_response(
ErrorType::InternalError,
"User-friendly message",
Some("/internal/path/file.rs:123 - Database connection failed"),
);
assert_eq!(error_response.error, "User-friendly message");
assert_eq!(error_response.error_type, ErrorType::InternalError);
let serialized =
serde_json::to_value(&error_response).expect("Failed to serialize error response");
assert_eq!(serialized["error_type"], "internal_error");
assert!(
error_response.details.is_none(),
"Internal details should never be exposed"
);
let error_response = create_safe_error_response(ErrorType::InternalError, "User message", None);
assert!(error_response.details.is_none());
}
#[test]
fn test_format_detection_security() {
use ref_solver::web::format_detection::{detect_format, FileFormat};
let dict_content = "@HD\tVN:1.0\tSO:coordinate\n@SQ\tSN:chr1\tLN:248956422\tM5:abc123\n";
let detected = detect_format(dict_content, Some("test.dict")).unwrap();
assert!(matches!(detected, FileFormat::Dict | FileFormat::Sam));
let malicious_content = "\x00\x01\x02\x03malicious binary content";
let result = detect_format(malicious_content, None);
assert!(result.is_ok() || result.is_err());
}
#[tokio::test]
async fn test_field_limit_concepts() {
use ref_solver::web::server::{MAX_FILE_FIELD_SIZE, MAX_MULTIPART_FIELDS, MAX_TEXT_FIELD_SIZE};
assert_eq!(MAX_MULTIPART_FIELDS, 10);
assert_eq!(MAX_FILE_FIELD_SIZE, 16 * 1024 * 1024);
assert_eq!(MAX_TEXT_FIELD_SIZE, 1024 * 1024);
}
#[test]
fn test_rate_limiting_configuration() {
let expected_per_second = 10; let expected_burst = 50;
assert!(
(1..=100).contains(&expected_per_second),
"Rate limit should be reasonable for legitimate use"
);
assert!(
expected_burst >= expected_per_second && expected_burst <= 1000,
"Burst size should be reasonable and >= per_second limit"
);
}
#[test]
fn test_security_headers_concepts() {
let expected_headers = vec![
"x-content-type-options",
"x-frame-options",
"x-xss-protection",
"strict-transport-security",
"referrer-policy",
];
for header in expected_headers {
assert!(
!header.is_empty(),
"Security header name should not be empty"
);
assert!(
header.starts_with('x') || header.contains('-'),
"Should be valid header format"
);
}
}
#[test]
fn test_validation_error_handling() {
use ref_solver::utils::validation::{validate_filename, ValidationError};
let long_filename = "a".repeat(300);
let test_cases = vec![
("", ValidationError::EmptyFilename),
(long_filename.as_str(), ValidationError::FilenameTooLong),
("../etc/passwd", ValidationError::InvalidFilename),
("test\0.txt", ValidationError::InvalidFilename),
];
for (input, expected_error_type) in test_cases {
let result = validate_filename(input);
assert!(result.is_err(), "Input '{input}' should fail validation");
let error = result.unwrap_err();
match (&error, &expected_error_type) {
(ValidationError::EmptyFilename, ValidationError::EmptyFilename)
| (ValidationError::FilenameTooLong, ValidationError::FilenameTooLong)
| (ValidationError::InvalidFilename, ValidationError::InvalidFilename) => {}
_ => panic!(
"Expected error type {expected_error_type:?} but got {error:?} for input '{input}'"
),
}
}
}